This commit is contained in:
Hillel Coren 2019-06-03 18:02:52 +03:00
parent 7821daf0c4
commit fdacfa4070
33 changed files with 2306 additions and 39 deletions

View File

@ -72,7 +72,7 @@ abstract class ExpenseEntity extends Object
shouldBeInvoiced: false, shouldBeInvoiced: false,
transactionId: '', transactionId: '',
transactionReference: '', transactionReference: '',
bankId: '', bankId: 0,
expenseCurrencyId: 0, expenseCurrencyId: 0,
exchangeCurrencyId: 0, exchangeCurrencyId: 0,
amount: 0.0, amount: 0.0,
@ -120,7 +120,7 @@ abstract class ExpenseEntity extends Object
String get transactionReference; String get transactionReference;
@BuiltValueField(wireName: 'bank_id') @BuiltValueField(wireName: 'bank_id')
String get bankId; int get bankId;
@BuiltValueField(wireName: 'expense_currency_id') @BuiltValueField(wireName: 'expense_currency_id')
int get expenseCurrencyId; int get expenseCurrencyId;
@ -136,7 +136,7 @@ abstract class ExpenseEntity extends Object
@BuiltValueField(wireName: 'exchange_rate') @BuiltValueField(wireName: 'exchange_rate')
double get exchangeRate; double get exchangeRate;
@BuiltValueField(wireName: 'invoiceCurrencyId') @BuiltValueField(wireName: 'invoice_currency_id')
int get invoiceCurrencyId; int get invoiceCurrencyId;
@BuiltValueField(wireName: 'tax_name1') @BuiltValueField(wireName: 'tax_name1')
@ -167,7 +167,8 @@ abstract class ExpenseEntity extends Object
@BuiltValueField(wireName: 'expense_category') @BuiltValueField(wireName: 'expense_category')
BuiltList<ExpenseCategoryEntity> get expenseCategories; BuiltList<ExpenseCategoryEntity> get expenseCategories;
List<EntityAction> getEntityActions({UserEntity user, ClientEntity client}) { List<EntityAction> getEntityActions(
{UserEntity user, ClientEntity client, bool includeEdit = false}) {
final actions = <EntityAction>[]; final actions = <EntityAction>[];
return actions..addAll(getBaseActions(user: user)); return actions..addAll(getBaseActions(user: user));

View File

@ -145,8 +145,7 @@ class _$ExpenseEntitySerializer implements StructuredSerializer<ExpenseEntity> {
serializers.serialize(object.transactionReference, serializers.serialize(object.transactionReference,
specifiedType: const FullType(String)), specifiedType: const FullType(String)),
'bank_id', 'bank_id',
serializers.serialize(object.bankId, serializers.serialize(object.bankId, specifiedType: const FullType(int)),
specifiedType: const FullType(String)),
'expense_currency_id', 'expense_currency_id',
serializers.serialize(object.expenseCurrencyId, serializers.serialize(object.expenseCurrencyId,
specifiedType: const FullType(int)), specifiedType: const FullType(int)),
@ -162,7 +161,7 @@ class _$ExpenseEntitySerializer implements StructuredSerializer<ExpenseEntity> {
'exchange_rate', 'exchange_rate',
serializers.serialize(object.exchangeRate, serializers.serialize(object.exchangeRate,
specifiedType: const FullType(double)), specifiedType: const FullType(double)),
'invoiceCurrencyId', 'invoice_currency_id',
serializers.serialize(object.invoiceCurrencyId, serializers.serialize(object.invoiceCurrencyId,
specifiedType: const FullType(int)), specifiedType: const FullType(int)),
'tax_name1', 'tax_name1',
@ -267,7 +266,7 @@ class _$ExpenseEntitySerializer implements StructuredSerializer<ExpenseEntity> {
break; break;
case 'bank_id': case 'bank_id':
result.bankId = serializers.deserialize(value, result.bankId = serializers.deserialize(value,
specifiedType: const FullType(String)) as String; specifiedType: const FullType(int)) as int;
break; break;
case 'expense_currency_id': case 'expense_currency_id':
result.expenseCurrencyId = serializers.deserialize(value, result.expenseCurrencyId = serializers.deserialize(value,
@ -289,7 +288,7 @@ class _$ExpenseEntitySerializer implements StructuredSerializer<ExpenseEntity> {
result.exchangeRate = serializers.deserialize(value, result.exchangeRate = serializers.deserialize(value,
specifiedType: const FullType(double)) as double; specifiedType: const FullType(double)) as double;
break; break;
case 'invoiceCurrencyId': case 'invoice_currency_id':
result.invoiceCurrencyId = serializers.deserialize(value, result.invoiceCurrencyId = serializers.deserialize(value,
specifiedType: const FullType(int)) as int; specifiedType: const FullType(int)) as int;
break; break;
@ -663,7 +662,7 @@ class _$ExpenseEntity extends ExpenseEntity {
@override @override
final String transactionReference; final String transactionReference;
@override @override
final String bankId; final int bankId;
@override @override
final int expenseCurrencyId; final int expenseCurrencyId;
@override @override
@ -948,9 +947,9 @@ class ExpenseEntityBuilder
set transactionReference(String transactionReference) => set transactionReference(String transactionReference) =>
_$this._transactionReference = transactionReference; _$this._transactionReference = transactionReference;
String _bankId; int _bankId;
String get bankId => _$this._bankId; int get bankId => _$this._bankId;
set bankId(String bankId) => _$this._bankId = bankId; set bankId(int bankId) => _$this._bankId = bankId;
int _expenseCurrencyId; int _expenseCurrencyId;
int get expenseCurrencyId => _$this._expenseCurrencyId; int get expenseCurrencyId => _$this._expenseCurrencyId;

View File

@ -23,6 +23,8 @@ import 'package:invoiceninja_flutter/redux/ui/ui_state.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_state.dart'; import 'package:invoiceninja_flutter/redux/invoice/invoice_state.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/expense/expense_state.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart'; import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart';
import 'package:invoiceninja_flutter/redux/task/task_state.dart'; import 'package:invoiceninja_flutter/redux/task/task_state.dart';
@ -78,6 +80,8 @@ part 'serializers.g.dart';
TimezoneItemResponse, TimezoneItemResponse,
TimezoneListResponse, TimezoneListResponse,
// STARTER: serializers - do not remove comment // STARTER: serializers - do not remove comment
ExpenseEntity,
VendorEntity, VendorEntity,
TaskEntity, TaskEntity,

View File

@ -59,6 +59,8 @@ Serializers _$serializers = (new Serializers().toBuilder()
..add(ExpenseEntity.serializer) ..add(ExpenseEntity.serializer)
..add(ExpenseItemResponse.serializer) ..add(ExpenseItemResponse.serializer)
..add(ExpenseListResponse.serializer) ..add(ExpenseListResponse.serializer)
..add(ExpenseState.serializer)
..add(ExpenseUIState.serializer)
..add(FrequencyEntity.serializer) ..add(FrequencyEntity.serializer)
..add(FrequencyItemResponse.serializer) ..add(FrequencyItemResponse.serializer)
..add(FrequencyListResponse.serializer) ..add(FrequencyListResponse.serializer)
@ -354,6 +356,8 @@ Serializers _$serializers = (new Serializers().toBuilder()
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(CountryEntity)]), () => new MapBuilder<int, CountryEntity>()) ..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(CountryEntity)]), () => new MapBuilder<int, CountryEntity>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(InvoiceStatusEntity)]), () => new MapBuilder<int, InvoiceStatusEntity>()) ..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(InvoiceStatusEntity)]), () => new MapBuilder<int, InvoiceStatusEntity>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(FrequencyEntity)]), () => new MapBuilder<int, FrequencyEntity>()) ..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(FrequencyEntity)]), () => new MapBuilder<int, FrequencyEntity>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(ExpenseEntity)]), () => new MapBuilder<int, ExpenseEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(InvoiceEntity)]), () => new MapBuilder<int, InvoiceEntity>()) ..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(InvoiceEntity)]), () => new MapBuilder<int, InvoiceEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>()) ..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(InvoiceEntity)]), () => new MapBuilder<int, InvoiceEntity>()) ..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(InvoiceEntity)]), () => new MapBuilder<int, InvoiceEntity>())

View File

@ -0,0 +1,67 @@
import 'dart:async';
import 'dart:convert';
import 'dart:core';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:invoiceninja_flutter/redux/auth/auth_state.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/data/web_client.dart';
class ExpenseRepository {
const ExpenseRepository({
this.webClient = const WebClient(),
});
final WebClient webClient;
Future<ExpenseEntity> loadItem(
CompanyEntity company, AuthState auth, int entityId) async {
final dynamic response =
await webClient.get('${auth.url}/expenses/$entityId', company.token);
final ExpenseItemResponse expenseResponse =
serializers.deserializeWith(ExpenseItemResponse.serializer, response);
return expenseResponse.data;
}
Future<BuiltList<ExpenseEntity>> loadList(
CompanyEntity company, AuthState auth, int updatedAt) async {
String url = auth.url + '/expenses?';
if (updatedAt > 0) {
url += '&updated_at=${updatedAt - kUpdatedAtBufferSeconds}';
}
final dynamic response = await webClient.get(url, company.token);
final ExpenseListResponse expenseResponse =
serializers.deserializeWith(ExpenseListResponse.serializer, response);
return expenseResponse.data;
}
Future<ExpenseEntity> saveData(
CompanyEntity company, AuthState auth, ExpenseEntity expense,
[EntityAction action]) async {
final data = serializers.serializeWith(ExpenseEntity.serializer, expense);
dynamic response;
if (expense.isNew) {
response = await webClient.post(
auth.url + '/expenses', company.token, json.encode(data));
} else {
var url = auth.url + '/expenses/' + expense.id.toString();
if (action != null) {
url += '?action=' + action.toString();
}
response = await webClient.put(url, company.token, json.encode(data));
}
final ExpenseItemResponse expenseResponse =
serializers.deserializeWith(ExpenseItemResponse.serializer, response);
return expenseResponse.data;
}
}

View File

@ -43,6 +43,12 @@ import 'package:local_auth/local_auth.dart';
//import 'package:quick_actions/quick_actions.dart'; //import 'package:quick_actions/quick_actions.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/ui/expense/expense_screen.dart';
import 'package:invoiceninja_flutter/ui/expense/edit/expense_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/expense/view/expense_view_vm.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_actions.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_middleware.dart';
import 'package:invoiceninja_flutter/ui/vendor/vendor_screen.dart'; import 'package:invoiceninja_flutter/ui/vendor/vendor_screen.dart';
import 'package:invoiceninja_flutter/ui/vendor/edit/vendor_edit_vm.dart'; import 'package:invoiceninja_flutter/ui/vendor/edit/vendor_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/vendor/view/vendor_view_vm.dart'; import 'package:invoiceninja_flutter/ui/vendor/view/vendor_view_vm.dart';
@ -97,6 +103,7 @@ void main() async {
..addAll(createStoreInvoicesMiddleware()) ..addAll(createStoreInvoicesMiddleware())
..addAll(createStorePersistenceMiddleware()) ..addAll(createStorePersistenceMiddleware())
// STARTER: middleware - do not remove comment // STARTER: middleware - do not remove comment
..addAll(createStoreExpensesMiddleware())
..addAll(createStoreVendorsMiddleware()) ..addAll(createStoreVendorsMiddleware())
..addAll(createStoreTasksMiddleware()) ..addAll(createStoreTasksMiddleware())
..addAll(createStoreProjectsMiddleware()) ..addAll(createStoreProjectsMiddleware())
@ -306,6 +313,13 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
InvoiceEditScreen.route: (context) => InvoiceEditScreen(), InvoiceEditScreen.route: (context) => InvoiceEditScreen(),
InvoiceEmailScreen.route: (context) => InvoiceEmailScreen(), InvoiceEmailScreen.route: (context) => InvoiceEmailScreen(),
// STARTER: routes - do not remove comment // STARTER: routes - do not remove comment
ExpenseScreen.route: (context) {
widget.store.dispatch(LoadExpenses());
return ExpenseScreen();
},
ExpenseViewScreen.route: (context) => ExpenseViewScreen(),
ExpenseEditScreen.route: (context) => ExpenseEditScreen(),
VendorScreen.route: (context) { VendorScreen.route: (context) {
widget.store.dispatch(LoadVendors()); widget.store.dispatch(LoadVendors());
return VendorScreen(); return VendorScreen();

View File

@ -13,6 +13,8 @@ import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart'; import 'package:built_value/serializer.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/expense/expense_state.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart'; import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart';
import 'package:invoiceninja_flutter/redux/task/task_state.dart'; import 'package:invoiceninja_flutter/redux/task/task_state.dart';
@ -109,6 +111,9 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
case EntityType.invoice: case EntityType.invoice:
return invoiceUIState; return invoiceUIState;
// STARTER: states switch - do not remove comment // STARTER: states switch - do not remove comment
case EntityType.expense:
return expenseUIState;
case EntityType.vendor: case EntityType.vendor:
return vendorUIState; return vendorUIState;
@ -150,6 +155,10 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
ListUIState get invoiceListState => uiState.invoiceUIState.listUIState; ListUIState get invoiceListState => uiState.invoiceUIState.listUIState;
// STARTER: state getters - do not remove comment // STARTER: state getters - do not remove comment
ExpenseState get expenseState => selectedCompanyState.expenseState;
ListUIState get expenseListState => uiState.expenseUIState.listUIState;
ExpenseUIState get expenseUIState => uiState.expenseUIState;
VendorState get vendorState => selectedCompanyState.vendorState; VendorState get vendorState => selectedCompanyState.vendorState;
ListUIState get vendorListState => uiState.vendorUIState.listUIState; ListUIState get vendorListState => uiState.vendorUIState.listUIState;
VendorUIState get vendorUIState => uiState.vendorUIState; VendorUIState get vendorUIState => uiState.vendorUIState;

View File

@ -9,6 +9,8 @@ import 'package:invoiceninja_flutter/redux/dashboard/dashboard_reducer.dart';
import 'package:invoiceninja_flutter/redux/company/company_actions.dart'; import 'package:invoiceninja_flutter/redux/company/company_actions.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/expense/expense_reducer.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_reducer.dart'; import 'package:invoiceninja_flutter/redux/vendor/vendor_reducer.dart';
import 'package:invoiceninja_flutter/redux/task/task_reducer.dart'; import 'package:invoiceninja_flutter/redux/task/task_reducer.dart';
@ -31,6 +33,7 @@ CompanyState companyReducer(CompanyState state, dynamic action) {
..productState.replace(productsReducer(state.productState, action)) ..productState.replace(productsReducer(state.productState, action))
..invoiceState.replace(invoicesReducer(state.invoiceState, action)) ..invoiceState.replace(invoicesReducer(state.invoiceState, action))
// STARTER: reducer - do not remove comment // STARTER: reducer - do not remove comment
..expenseState.replace(expensesReducer(state.expenseState, action))
..vendorState.replace(vendorsReducer(state.vendorState, action)) ..vendorState.replace(vendorsReducer(state.vendorState, action))
..taskState.replace(tasksReducer(state.taskState, action)) ..taskState.replace(tasksReducer(state.taskState, action))
..projectState.replace(projectsReducer(state.projectState, action)) ..projectState.replace(projectsReducer(state.projectState, action))

View File

@ -7,6 +7,8 @@ import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart'; import 'package:built_value/serializer.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/expense/expense_state.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart'; import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart';
import 'package:invoiceninja_flutter/redux/task/task_state.dart'; import 'package:invoiceninja_flutter/redux/task/task_state.dart';
@ -29,6 +31,8 @@ abstract class CompanyState
clientState: ClientState(), clientState: ClientState(),
invoiceState: InvoiceState(), invoiceState: InvoiceState(),
// STARTER: constructor - do not remove comment // STARTER: constructor - do not remove comment
expenseState: ExpenseState(),
vendorState: VendorState(), vendorState: VendorState(),
taskState: TaskState(), taskState: TaskState(),
@ -52,6 +56,8 @@ abstract class CompanyState
InvoiceState get invoiceState; InvoiceState get invoiceState;
// STARTER: fields - do not remove comment // STARTER: fields - do not remove comment
ExpenseState get expenseState;
VendorState get vendorState; VendorState get vendorState;
TaskState get taskState; TaskState get taskState;

View File

@ -44,6 +44,9 @@ class _$CompanyStateSerializer implements StructuredSerializer<CompanyState> {
'invoiceState', 'invoiceState',
serializers.serialize(object.invoiceState, serializers.serialize(object.invoiceState,
specifiedType: const FullType(InvoiceState)), specifiedType: const FullType(InvoiceState)),
'expenseState',
serializers.serialize(object.expenseState,
specifiedType: const FullType(ExpenseState)),
'vendorState', 'vendorState',
serializers.serialize(object.vendorState, serializers.serialize(object.vendorState,
specifiedType: const FullType(VendorState)), specifiedType: const FullType(VendorState)),
@ -101,6 +104,10 @@ class _$CompanyStateSerializer implements StructuredSerializer<CompanyState> {
result.invoiceState.replace(serializers.deserialize(value, result.invoiceState.replace(serializers.deserialize(value,
specifiedType: const FullType(InvoiceState)) as InvoiceState); specifiedType: const FullType(InvoiceState)) as InvoiceState);
break; break;
case 'expenseState':
result.expenseState.replace(serializers.deserialize(value,
specifiedType: const FullType(ExpenseState)) as ExpenseState);
break;
case 'vendorState': case 'vendorState':
result.vendorState.replace(serializers.deserialize(value, result.vendorState.replace(serializers.deserialize(value,
specifiedType: const FullType(VendorState)) as VendorState); specifiedType: const FullType(VendorState)) as VendorState);
@ -140,6 +147,8 @@ class _$CompanyState extends CompanyState {
@override @override
final InvoiceState invoiceState; final InvoiceState invoiceState;
@override @override
final ExpenseState expenseState;
@override
final VendorState vendorState; final VendorState vendorState;
@override @override
final TaskState taskState; final TaskState taskState;
@ -159,6 +168,7 @@ class _$CompanyState extends CompanyState {
this.productState, this.productState,
this.clientState, this.clientState,
this.invoiceState, this.invoiceState,
this.expenseState,
this.vendorState, this.vendorState,
this.taskState, this.taskState,
this.projectState, this.projectState,
@ -177,6 +187,9 @@ class _$CompanyState extends CompanyState {
if (invoiceState == null) { if (invoiceState == null) {
throw new BuiltValueNullFieldError('CompanyState', 'invoiceState'); throw new BuiltValueNullFieldError('CompanyState', 'invoiceState');
} }
if (expenseState == null) {
throw new BuiltValueNullFieldError('CompanyState', 'expenseState');
}
if (vendorState == null) { if (vendorState == null) {
throw new BuiltValueNullFieldError('CompanyState', 'vendorState'); throw new BuiltValueNullFieldError('CompanyState', 'vendorState');
} }
@ -210,6 +223,7 @@ class _$CompanyState extends CompanyState {
productState == other.productState && productState == other.productState &&
clientState == other.clientState && clientState == other.clientState &&
invoiceState == other.invoiceState && invoiceState == other.invoiceState &&
expenseState == other.expenseState &&
vendorState == other.vendorState && vendorState == other.vendorState &&
taskState == other.taskState && taskState == other.taskState &&
projectState == other.projectState && projectState == other.projectState &&
@ -227,11 +241,13 @@ class _$CompanyState extends CompanyState {
$jc( $jc(
$jc( $jc(
$jc( $jc(
$jc($jc(0, company.hashCode), $jc(
dashboardState.hashCode), $jc($jc(0, company.hashCode),
productState.hashCode), dashboardState.hashCode),
clientState.hashCode), productState.hashCode),
invoiceState.hashCode), clientState.hashCode),
invoiceState.hashCode),
expenseState.hashCode),
vendorState.hashCode), vendorState.hashCode),
taskState.hashCode), taskState.hashCode),
projectState.hashCode), projectState.hashCode),
@ -247,6 +263,7 @@ class _$CompanyState extends CompanyState {
..add('productState', productState) ..add('productState', productState)
..add('clientState', clientState) ..add('clientState', clientState)
..add('invoiceState', invoiceState) ..add('invoiceState', invoiceState)
..add('expenseState', expenseState)
..add('vendorState', vendorState) ..add('vendorState', vendorState)
..add('taskState', taskState) ..add('taskState', taskState)
..add('projectState', projectState) ..add('projectState', projectState)
@ -289,6 +306,12 @@ class CompanyStateBuilder
set invoiceState(InvoiceStateBuilder invoiceState) => set invoiceState(InvoiceStateBuilder invoiceState) =>
_$this._invoiceState = invoiceState; _$this._invoiceState = invoiceState;
ExpenseStateBuilder _expenseState;
ExpenseStateBuilder get expenseState =>
_$this._expenseState ??= new ExpenseStateBuilder();
set expenseState(ExpenseStateBuilder expenseState) =>
_$this._expenseState = expenseState;
VendorStateBuilder _vendorState; VendorStateBuilder _vendorState;
VendorStateBuilder get vendorState => VendorStateBuilder get vendorState =>
_$this._vendorState ??= new VendorStateBuilder(); _$this._vendorState ??= new VendorStateBuilder();
@ -327,6 +350,7 @@ class CompanyStateBuilder
_productState = _$v.productState?.toBuilder(); _productState = _$v.productState?.toBuilder();
_clientState = _$v.clientState?.toBuilder(); _clientState = _$v.clientState?.toBuilder();
_invoiceState = _$v.invoiceState?.toBuilder(); _invoiceState = _$v.invoiceState?.toBuilder();
_expenseState = _$v.expenseState?.toBuilder();
_vendorState = _$v.vendorState?.toBuilder(); _vendorState = _$v.vendorState?.toBuilder();
_taskState = _$v.taskState?.toBuilder(); _taskState = _$v.taskState?.toBuilder();
_projectState = _$v.projectState?.toBuilder(); _projectState = _$v.projectState?.toBuilder();
@ -361,6 +385,7 @@ class CompanyStateBuilder
productState: productState.build(), productState: productState.build(),
clientState: clientState.build(), clientState: clientState.build(),
invoiceState: invoiceState.build(), invoiceState: invoiceState.build(),
expenseState: expenseState.build(),
vendorState: vendorState.build(), vendorState: vendorState.build(),
taskState: taskState.build(), taskState: taskState.build(),
projectState: projectState.build(), projectState: projectState.build(),
@ -379,6 +404,8 @@ class CompanyStateBuilder
clientState.build(); clientState.build();
_$failedField = 'invoiceState'; _$failedField = 'invoiceState';
invoiceState.build(); invoiceState.build();
_$failedField = 'expenseState';
expenseState.build();
_$failedField = 'vendorState'; _$failedField = 'vendorState';
vendorState.build(); vendorState.build();
_$failedField = 'taskState'; _$failedField = 'taskState';

View File

@ -0,0 +1,223 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
class ViewExpenseList implements PersistUI {
ViewExpenseList(this.context);
final BuildContext context;
}
class ViewExpense implements PersistUI {
ViewExpense({this.expenseId, this.context});
final int expenseId;
final BuildContext context;
}
class EditExpense implements PersistUI {
EditExpense(
{this.expense, this.context, this.completer, this.trackRoute = true});
final ExpenseEntity expense;
final BuildContext context;
final Completer completer;
final bool trackRoute;
}
class UpdateExpense implements PersistUI {
UpdateExpense(this.expense);
final ExpenseEntity expense;
}
class LoadExpense {
LoadExpense({this.completer, this.expenseId, this.loadActivities = false});
final Completer completer;
final int expenseId;
final bool loadActivities;
}
class LoadExpenseActivity {
LoadExpenseActivity({this.completer, this.expenseId});
final Completer completer;
final int expenseId;
}
class LoadExpenses {
LoadExpenses({this.completer, this.force = false});
final Completer completer;
final bool force;
}
class LoadExpenseRequest implements StartLoading {}
class LoadExpenseFailure implements StopLoading {
LoadExpenseFailure(this.error);
final dynamic error;
@override
String toString() {
return 'LoadExpenseFailure{error: $error}';
}
}
class LoadExpenseSuccess implements StopLoading, PersistData {
LoadExpenseSuccess(this.expense);
final ExpenseEntity expense;
@override
String toString() {
return 'LoadExpenseSuccess{expense: $expense}';
}
}
class LoadExpensesRequest implements StartLoading {}
class LoadExpensesFailure implements StopLoading {
LoadExpensesFailure(this.error);
final dynamic error;
@override
String toString() {
return 'LoadExpensesFailure{error: $error}';
}
}
class LoadExpensesSuccess implements StopLoading, PersistData {
LoadExpensesSuccess(this.expenses);
final BuiltList<ExpenseEntity> expenses;
@override
String toString() {
return 'LoadExpensesSuccess{expenses: $expenses}';
}
}
class SaveExpenseRequest implements StartSaving {
SaveExpenseRequest({this.completer, this.expense});
final Completer completer;
final ExpenseEntity expense;
}
class SaveExpenseSuccess implements StopSaving, PersistData, PersistUI {
SaveExpenseSuccess(this.expense);
final ExpenseEntity expense;
}
class AddExpenseSuccess implements StopSaving, PersistData, PersistUI {
AddExpenseSuccess(this.expense);
final ExpenseEntity expense;
}
class SaveExpenseFailure implements StopSaving {
SaveExpenseFailure(this.error);
final Object error;
}
class ArchiveExpenseRequest implements StartSaving {
ArchiveExpenseRequest(this.completer, this.expenseId);
final Completer completer;
final int expenseId;
}
class ArchiveExpenseSuccess implements StopSaving, PersistData {
ArchiveExpenseSuccess(this.expense);
final ExpenseEntity expense;
}
class ArchiveExpenseFailure implements StopSaving {
ArchiveExpenseFailure(this.expense);
final ExpenseEntity expense;
}
class DeleteExpenseRequest implements StartSaving {
DeleteExpenseRequest(this.completer, this.expenseId);
final Completer completer;
final int expenseId;
}
class DeleteExpenseSuccess implements StopSaving, PersistData {
DeleteExpenseSuccess(this.expense);
final ExpenseEntity expense;
}
class DeleteExpenseFailure implements StopSaving {
DeleteExpenseFailure(this.expense);
final ExpenseEntity expense;
}
class RestoreExpenseRequest implements StartSaving {
RestoreExpenseRequest(this.completer, this.expenseId);
final Completer completer;
final int expenseId;
}
class RestoreExpenseSuccess implements StopSaving, PersistData {
RestoreExpenseSuccess(this.expense);
final ExpenseEntity expense;
}
class RestoreExpenseFailure implements StopSaving {
RestoreExpenseFailure(this.expense);
final ExpenseEntity expense;
}
class FilterExpenses {
FilterExpenses(this.filter);
final String filter;
}
class SortExpenses implements PersistUI {
SortExpenses(this.field);
final String field;
}
class FilterExpensesByState implements PersistUI {
FilterExpensesByState(this.state);
final EntityState state;
}
class FilterExpensesByCustom1 implements PersistUI {
FilterExpensesByCustom1(this.value);
final String value;
}
class FilterExpensesByCustom2 implements PersistUI {
FilterExpensesByCustom2(this.value);
final String value;
}
class FilterExpensesByEntity implements PersistUI {
FilterExpensesByEntity({this.entityId, this.entityType});
final int entityId;
final EntityType entityType;
}

View File

@ -0,0 +1,234 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/expense/expense_screen.dart';
import 'package:invoiceninja_flutter/ui/expense/edit/expense_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/expense/view/expense_view_vm.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/repositories/expense_repository.dart';
List<Middleware<AppState>> createStoreExpensesMiddleware([
ExpenseRepository repository = const ExpenseRepository(),
]) {
final viewExpenseList = _viewExpenseList();
final viewExpense = _viewExpense();
final editExpense = _editExpense();
final loadExpenses = _loadExpenses(repository);
final loadExpense = _loadExpense(repository);
final saveExpense = _saveExpense(repository);
final archiveExpense = _archiveExpense(repository);
final deleteExpense = _deleteExpense(repository);
final restoreExpense = _restoreExpense(repository);
return [
TypedMiddleware<AppState, ViewExpenseList>(viewExpenseList),
TypedMiddleware<AppState, ViewExpense>(viewExpense),
TypedMiddleware<AppState, EditExpense>(editExpense),
TypedMiddleware<AppState, LoadExpenses>(loadExpenses),
TypedMiddleware<AppState, LoadExpense>(loadExpense),
TypedMiddleware<AppState, SaveExpenseRequest>(saveExpense),
TypedMiddleware<AppState, ArchiveExpenseRequest>(archiveExpense),
TypedMiddleware<AppState, DeleteExpenseRequest>(deleteExpense),
TypedMiddleware<AppState, RestoreExpenseRequest>(restoreExpense),
];
}
Middleware<AppState> _editExpense() {
return (Store<AppState> store, dynamic action, NextDispatcher next) async {
next(action);
store.dispatch(UpdateCurrentRoute(ExpenseEditScreen.route));
final expense =
await Navigator.of(action.context).pushNamed(ExpenseEditScreen.route);
if (action.completer != null && expense != null) {
action.completer.complete(expense);
}
};
}
Middleware<AppState> _viewExpense() {
return (Store<AppState> store, dynamic action, NextDispatcher next) async {
next(action);
store.dispatch(UpdateCurrentRoute(ExpenseViewScreen.route));
Navigator.of(action.context).pushNamed(ExpenseViewScreen.route);
};
}
Middleware<AppState> _viewExpenseList() {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
next(action);
store.dispatch(UpdateCurrentRoute(ExpenseScreen.route));
Navigator.of(action.context).pushNamedAndRemoveUntil(
ExpenseScreen.route, (Route<dynamic> route) => false);
};
}
Middleware<AppState> _archiveExpense(ExpenseRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
final origExpense = store.state.expenseState.map[action.expenseId];
repository
.saveData(store.state.selectedCompany, store.state.authState,
origExpense, EntityAction.archive)
.then((ExpenseEntity expense) {
store.dispatch(ArchiveExpenseSuccess(expense));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(ArchiveExpenseFailure(origExpense));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _deleteExpense(ExpenseRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
final origExpense = store.state.expenseState.map[action.expenseId];
repository
.saveData(store.state.selectedCompany, store.state.authState,
origExpense, EntityAction.delete)
.then((ExpenseEntity expense) {
store.dispatch(DeleteExpenseSuccess(expense));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(DeleteExpenseFailure(origExpense));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _restoreExpense(ExpenseRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
final origExpense = store.state.expenseState.map[action.expenseId];
repository
.saveData(store.state.selectedCompany, store.state.authState,
origExpense, EntityAction.restore)
.then((ExpenseEntity expense) {
store.dispatch(RestoreExpenseSuccess(expense));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(RestoreExpenseFailure(origExpense));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _saveExpense(ExpenseRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
repository
.saveData(
store.state.selectedCompany, store.state.authState, action.expense)
.then((ExpenseEntity expense) {
if (action.expense.isNew) {
store.dispatch(AddExpenseSuccess(expense));
} else {
store.dispatch(SaveExpenseSuccess(expense));
}
action.completer.complete(expense);
}).catchError((Object error) {
print(error);
store.dispatch(SaveExpenseFailure(error));
action.completer.completeError(error);
});
next(action);
};
}
Middleware<AppState> _loadExpense(ExpenseRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
final AppState state = store.state;
if (state.isLoading) {
next(action);
return;
}
store.dispatch(LoadExpenseRequest());
repository
.loadItem(state.selectedCompany, state.authState, action.expenseId)
.then((expense) {
store.dispatch(LoadExpenseSuccess(expense));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(LoadExpenseFailure(error));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _loadExpenses(ExpenseRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
final AppState state = store.state;
if (!state.expenseState.isStale && !action.force) {
next(action);
return;
}
if (state.isLoading) {
next(action);
return;
}
final int updatedAt = (state.expenseState.lastUpdated / 1000).round();
store.dispatch(LoadExpensesRequest());
repository
.loadList(state.selectedCompany, state.authState, updatedAt)
.then((data) {
store.dispatch(LoadExpensesSuccess(data));
if (action.completer != null) {
action.completer.complete(null);
}
/*
if (state.productState.isStale) {
store.dispatch(LoadProducts());
}
*/
}).catchError((Object error) {
print(error);
store.dispatch(LoadExpensesFailure(error));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}

View File

@ -0,0 +1,205 @@
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/company/company_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/entity_ui_state.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_actions.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_state.dart';
EntityUIState expenseUIReducer(ExpenseUIState state, dynamic action) {
return state.rebuild((b) => b
..listUIState.replace(expenseListReducer(state.listUIState, action))
..editing.replace(editingReducer(state.editing, action))
..selectedId = selectedIdReducer(state.selectedId, action));
}
Reducer<int> selectedIdReducer = combineReducers([
TypedReducer<int, ViewExpense>(
(int selectedId, dynamic action) => action.expenseId),
TypedReducer<int, AddExpenseSuccess>(
(int selectedId, dynamic action) => action.expense.id),
]);
final editingReducer = combineReducers<ExpenseEntity>([
TypedReducer<ExpenseEntity, SaveExpenseSuccess>(_updateEditing),
TypedReducer<ExpenseEntity, AddExpenseSuccess>(_updateEditing),
TypedReducer<ExpenseEntity, RestoreExpenseSuccess>(_updateEditing),
TypedReducer<ExpenseEntity, ArchiveExpenseSuccess>(_updateEditing),
TypedReducer<ExpenseEntity, DeleteExpenseSuccess>(_updateEditing),
TypedReducer<ExpenseEntity, EditExpense>(_updateEditing),
TypedReducer<ExpenseEntity, UpdateExpense>(_updateEditing),
TypedReducer<ExpenseEntity, SelectCompany>(_clearEditing),
]);
ExpenseEntity _clearEditing(ExpenseEntity expense, dynamic action) {
return ExpenseEntity();
}
ExpenseEntity _updateEditing(ExpenseEntity expense, dynamic action) {
return action.expense;
}
final expenseListReducer = combineReducers<ListUIState>([
TypedReducer<ListUIState, SortExpenses>(_sortExpenses),
TypedReducer<ListUIState, FilterExpensesByState>(_filterExpensesByState),
TypedReducer<ListUIState, FilterExpenses>(_filterExpenses),
TypedReducer<ListUIState, FilterExpensesByCustom1>(_filterExpensesByCustom1),
TypedReducer<ListUIState, FilterExpensesByCustom2>(_filterExpensesByCustom2),
TypedReducer<ListUIState, FilterExpensesByEntity>(_filterExpensesByClient),
]);
ListUIState _filterExpensesByClient(
ListUIState expenseListState, FilterExpensesByEntity action) {
return expenseListState.rebuild((b) => b
..filterEntityId = action.entityId
..filterEntityType = action.entityType);
}
ListUIState _filterExpensesByCustom1(
ListUIState expenseListState, FilterExpensesByCustom1 action) {
if (expenseListState.custom1Filters.contains(action.value)) {
return expenseListState
.rebuild((b) => b..custom1Filters.remove(action.value));
} else {
return expenseListState.rebuild((b) => b..custom1Filters.add(action.value));
}
}
ListUIState _filterExpensesByCustom2(
ListUIState expenseListState, FilterExpensesByCustom2 action) {
if (expenseListState.custom2Filters.contains(action.value)) {
return expenseListState
.rebuild((b) => b..custom2Filters.remove(action.value));
} else {
return expenseListState.rebuild((b) => b..custom2Filters.add(action.value));
}
}
ListUIState _filterExpensesByState(
ListUIState expenseListState, FilterExpensesByState action) {
if (expenseListState.stateFilters.contains(action.state)) {
return expenseListState
.rebuild((b) => b..stateFilters.remove(action.state));
} else {
return expenseListState.rebuild((b) => b..stateFilters.add(action.state));
}
}
ListUIState _filterExpenses(
ListUIState expenseListState, FilterExpenses action) {
return expenseListState.rebuild((b) => b..filter = action.filter);
}
ListUIState _sortExpenses(ListUIState expenseListState, SortExpenses action) {
return expenseListState.rebuild((b) => b
..sortAscending = b.sortField != action.field || !b.sortAscending
..sortField = action.field);
}
final expensesReducer = combineReducers<ExpenseState>([
TypedReducer<ExpenseState, SaveExpenseSuccess>(_updateExpense),
TypedReducer<ExpenseState, AddExpenseSuccess>(_addExpense),
TypedReducer<ExpenseState, LoadExpensesSuccess>(_setLoadedExpenses),
TypedReducer<ExpenseState, LoadExpenseSuccess>(_setLoadedExpense),
TypedReducer<ExpenseState, ArchiveExpenseRequest>(_archiveExpenseRequest),
TypedReducer<ExpenseState, ArchiveExpenseSuccess>(_archiveExpenseSuccess),
TypedReducer<ExpenseState, ArchiveExpenseFailure>(_archiveExpenseFailure),
TypedReducer<ExpenseState, DeleteExpenseRequest>(_deleteExpenseRequest),
TypedReducer<ExpenseState, DeleteExpenseSuccess>(_deleteExpenseSuccess),
TypedReducer<ExpenseState, DeleteExpenseFailure>(_deleteExpenseFailure),
TypedReducer<ExpenseState, RestoreExpenseRequest>(_restoreExpenseRequest),
TypedReducer<ExpenseState, RestoreExpenseSuccess>(_restoreExpenseSuccess),
TypedReducer<ExpenseState, RestoreExpenseFailure>(_restoreExpenseFailure),
]);
ExpenseState _archiveExpenseRequest(
ExpenseState expenseState, ArchiveExpenseRequest action) {
final expense = expenseState.map[action.expenseId]
.rebuild((b) => b..archivedAt = DateTime.now().millisecondsSinceEpoch);
return expenseState.rebuild((b) => b..map[action.expenseId] = expense);
}
ExpenseState _archiveExpenseSuccess(
ExpenseState expenseState, ArchiveExpenseSuccess action) {
return expenseState
.rebuild((b) => b..map[action.expense.id] = action.expense);
}
ExpenseState _archiveExpenseFailure(
ExpenseState expenseState, ArchiveExpenseFailure action) {
return expenseState
.rebuild((b) => b..map[action.expense.id] = action.expense);
}
ExpenseState _deleteExpenseRequest(
ExpenseState expenseState, DeleteExpenseRequest action) {
final expense = expenseState.map[action.expenseId].rebuild((b) => b
..archivedAt = DateTime.now().millisecondsSinceEpoch
..isDeleted = true);
return expenseState.rebuild((b) => b..map[action.expenseId] = expense);
}
ExpenseState _deleteExpenseSuccess(
ExpenseState expenseState, DeleteExpenseSuccess action) {
return expenseState
.rebuild((b) => b..map[action.expense.id] = action.expense);
}
ExpenseState _deleteExpenseFailure(
ExpenseState expenseState, DeleteExpenseFailure action) {
return expenseState
.rebuild((b) => b..map[action.expense.id] = action.expense);
}
ExpenseState _restoreExpenseRequest(
ExpenseState expenseState, RestoreExpenseRequest action) {
final expense = expenseState.map[action.expenseId].rebuild((b) => b
..archivedAt = null
..isDeleted = false);
return expenseState.rebuild((b) => b..map[action.expenseId] = expense);
}
ExpenseState _restoreExpenseSuccess(
ExpenseState expenseState, RestoreExpenseSuccess action) {
return expenseState
.rebuild((b) => b..map[action.expense.id] = action.expense);
}
ExpenseState _restoreExpenseFailure(
ExpenseState expenseState, RestoreExpenseFailure action) {
return expenseState
.rebuild((b) => b..map[action.expense.id] = action.expense);
}
ExpenseState _addExpense(ExpenseState expenseState, AddExpenseSuccess action) {
return expenseState.rebuild((b) => b
..map[action.expense.id] = action.expense
..list.add(action.expense.id));
}
ExpenseState _updateExpense(
ExpenseState expenseState, SaveExpenseSuccess action) {
return expenseState
.rebuild((b) => b..map[action.expense.id] = action.expense);
}
ExpenseState _setLoadedExpense(
ExpenseState expenseState, LoadExpenseSuccess action) {
return expenseState
.rebuild((b) => b..map[action.expense.id] = action.expense);
}
ExpenseState _setLoadedExpenses(
ExpenseState expenseState, LoadExpensesSuccess action) {
final state = expenseState.rebuild((b) => b
..lastUpdated = DateTime.now().millisecondsSinceEpoch
..map.addAll(Map.fromIterable(
action.expenses,
key: (dynamic item) => item.id,
value: (dynamic item) => item,
)));
return state.rebuild((b) => b..list.replace(state.map.keys));
}

View File

@ -0,0 +1,75 @@
import 'package:memoize/memoize.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
var memoizedDropdownExpenseList = memo3(
(BuiltMap<int, ExpenseEntity> expenseMap, BuiltList<int> expenseList,
int clientId) =>
dropdownExpensesSelector(expenseMap, expenseList, clientId));
List<int> dropdownExpensesSelector(BuiltMap<int, ExpenseEntity> expenseMap,
BuiltList<int> expenseList, int clientId) {
final list = expenseList.where((expenseId) {
final expense = expenseMap[expenseId];
/*
if (clientId != null && clientId > 0 && expense.clientId != clientId) {
return false;
}
*/
return expense.isActive;
}).toList();
list.sort((expenseAId, expenseBId) {
final expenseA = expenseMap[expenseAId];
final expenseB = expenseMap[expenseBId];
return expenseA.compareTo(expenseB, ExpenseFields.expenseDate, true);
});
return list;
}
var memoizedFilteredExpenseList = memo3(
(BuiltMap<int, ExpenseEntity> expenseMap, BuiltList<int> expenseList,
ListUIState expenseListState) =>
filteredExpensesSelector(expenseMap, expenseList, expenseListState));
List<int> filteredExpensesSelector(BuiltMap<int, ExpenseEntity> expenseMap,
BuiltList<int> expenseList, ListUIState expenseListState) {
final list = expenseList.where((expenseId) {
final expense = expenseMap[expenseId];
if (!expense.matchesStates(expenseListState.stateFilters)) {
return false;
}
/*
if (expenseListState.filterEntityId != null &&
expense.clientId != expenseListState.filterEntityId) {
return false;
}
*/
if (expenseListState.custom1Filters.isNotEmpty &&
!expenseListState.custom1Filters.contains(expense.customValue1)) {
return false;
}
if (expenseListState.custom2Filters.isNotEmpty &&
!expenseListState.custom2Filters.contains(expense.customValue2)) {
return false;
}
/*
if (expenseListState.filterEntityId != null &&
expense.entityId != expenseListState.filterEntityId) {
return false;
}
*/
return expense.matchesFilter(expenseListState.filter);
}).toList();
list.sort((expenseAId, expenseBId) {
final expenseA = expenseMap[expenseAId];
final expenseB = expenseMap[expenseBId];
return expenseA.compareTo(
expenseB, expenseListState.sortField, expenseListState.sortAscending);
});
return list;
}

View File

@ -0,0 +1,62 @@
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/expense_model.dart';
import 'package:invoiceninja_flutter/redux/ui/entity_ui_state.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
part 'expense_state.g.dart';
abstract class ExpenseState
implements Built<ExpenseState, ExpenseStateBuilder> {
factory ExpenseState() {
return _$ExpenseState._(
lastUpdated: 0,
map: BuiltMap<int, ExpenseEntity>(),
list: BuiltList<int>(),
);
}
ExpenseState._();
@nullable
int get lastUpdated;
BuiltMap<int, ExpenseEntity> get map;
BuiltList<int> get list;
bool get isStale {
if (!isLoaded) {
return true;
}
return DateTime.now().millisecondsSinceEpoch - lastUpdated >
kMillisecondsToRefreshData;
}
bool get isLoaded => lastUpdated != null && lastUpdated > 0;
static Serializer<ExpenseState> get serializer => _$expenseStateSerializer;
}
abstract class ExpenseUIState extends Object
with EntityUIState
implements Built<ExpenseUIState, ExpenseUIStateBuilder> {
factory ExpenseUIState() {
return _$ExpenseUIState._(
listUIState: ListUIState(ExpenseFields.expenseDate),
editing: ExpenseEntity(),
selectedId: 0,
);
}
ExpenseUIState._();
@nullable
ExpenseEntity get editing;
@override
bool get isCreatingNew => editing.isNew;
static Serializer<ExpenseUIState> get serializer =>
_$expenseUIStateSerializer;
}

View File

@ -0,0 +1,392 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'expense_state.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
// ignore_for_file: always_put_control_body_on_new_line
// ignore_for_file: annotate_overrides
// ignore_for_file: avoid_annotating_with_dynamic
// ignore_for_file: avoid_catches_without_on_clauses
// ignore_for_file: avoid_returning_this
// ignore_for_file: lines_longer_than_80_chars
// ignore_for_file: omit_local_variable_types
// ignore_for_file: prefer_expression_function_bodies
// ignore_for_file: sort_constructors_first
// ignore_for_file: unnecessary_const
// ignore_for_file: unnecessary_new
// ignore_for_file: test_types_in_equals
Serializer<ExpenseState> _$expenseStateSerializer =
new _$ExpenseStateSerializer();
Serializer<ExpenseUIState> _$expenseUIStateSerializer =
new _$ExpenseUIStateSerializer();
class _$ExpenseStateSerializer implements StructuredSerializer<ExpenseState> {
@override
final Iterable<Type> types = const [ExpenseState, _$ExpenseState];
@override
final String wireName = 'ExpenseState';
@override
Iterable serialize(Serializers serializers, ExpenseState object,
{FullType specifiedType = FullType.unspecified}) {
final result = <Object>[
'map',
serializers.serialize(object.map,
specifiedType: const FullType(BuiltMap,
const [const FullType(int), const FullType(ExpenseEntity)])),
'list',
serializers.serialize(object.list,
specifiedType:
const FullType(BuiltList, const [const FullType(int)])),
];
if (object.lastUpdated != null) {
result
..add('lastUpdated')
..add(serializers.serialize(object.lastUpdated,
specifiedType: const FullType(int)));
}
return result;
}
@override
ExpenseState deserialize(Serializers serializers, Iterable serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new ExpenseStateBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current as String;
iterator.moveNext();
final dynamic value = iterator.current;
switch (key) {
case 'lastUpdated':
result.lastUpdated = serializers.deserialize(value,
specifiedType: const FullType(int)) as int;
break;
case 'map':
result.map.replace(serializers.deserialize(value,
specifiedType: const FullType(BuiltMap, const [
const FullType(int),
const FullType(ExpenseEntity)
])) as BuiltMap);
break;
case 'list':
result.list.replace(serializers.deserialize(value,
specifiedType:
const FullType(BuiltList, const [const FullType(int)]))
as BuiltList);
break;
}
}
return result.build();
}
}
class _$ExpenseUIStateSerializer
implements StructuredSerializer<ExpenseUIState> {
@override
final Iterable<Type> types = const [ExpenseUIState, _$ExpenseUIState];
@override
final String wireName = 'ExpenseUIState';
@override
Iterable serialize(Serializers serializers, ExpenseUIState object,
{FullType specifiedType = FullType.unspecified}) {
final result = <Object>[
'selectedId',
serializers.serialize(object.selectedId,
specifiedType: const FullType(int)),
'listUIState',
serializers.serialize(object.listUIState,
specifiedType: const FullType(ListUIState)),
];
if (object.editing != null) {
result
..add('editing')
..add(serializers.serialize(object.editing,
specifiedType: const FullType(ExpenseEntity)));
}
return result;
}
@override
ExpenseUIState deserialize(Serializers serializers, Iterable serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new ExpenseUIStateBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current as String;
iterator.moveNext();
final dynamic value = iterator.current;
switch (key) {
case 'editing':
result.editing.replace(serializers.deserialize(value,
specifiedType: const FullType(ExpenseEntity)) as ExpenseEntity);
break;
case 'selectedId':
result.selectedId = serializers.deserialize(value,
specifiedType: const FullType(int)) as int;
break;
case 'listUIState':
result.listUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(ListUIState)) as ListUIState);
break;
}
}
return result.build();
}
}
class _$ExpenseState extends ExpenseState {
@override
final int lastUpdated;
@override
final BuiltMap<int, ExpenseEntity> map;
@override
final BuiltList<int> list;
factory _$ExpenseState([void updates(ExpenseStateBuilder b)]) =>
(new ExpenseStateBuilder()..update(updates)).build();
_$ExpenseState._({this.lastUpdated, this.map, this.list}) : super._() {
if (map == null) {
throw new BuiltValueNullFieldError('ExpenseState', 'map');
}
if (list == null) {
throw new BuiltValueNullFieldError('ExpenseState', 'list');
}
}
@override
ExpenseState rebuild(void updates(ExpenseStateBuilder b)) =>
(toBuilder()..update(updates)).build();
@override
ExpenseStateBuilder toBuilder() => new ExpenseStateBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is ExpenseState &&
lastUpdated == other.lastUpdated &&
map == other.map &&
list == other.list;
}
@override
int get hashCode {
return $jf(
$jc($jc($jc(0, lastUpdated.hashCode), map.hashCode), list.hashCode));
}
@override
String toString() {
return (newBuiltValueToStringHelper('ExpenseState')
..add('lastUpdated', lastUpdated)
..add('map', map)
..add('list', list))
.toString();
}
}
class ExpenseStateBuilder
implements Builder<ExpenseState, ExpenseStateBuilder> {
_$ExpenseState _$v;
int _lastUpdated;
int get lastUpdated => _$this._lastUpdated;
set lastUpdated(int lastUpdated) => _$this._lastUpdated = lastUpdated;
MapBuilder<int, ExpenseEntity> _map;
MapBuilder<int, ExpenseEntity> get map =>
_$this._map ??= new MapBuilder<int, ExpenseEntity>();
set map(MapBuilder<int, ExpenseEntity> map) => _$this._map = map;
ListBuilder<int> _list;
ListBuilder<int> get list => _$this._list ??= new ListBuilder<int>();
set list(ListBuilder<int> list) => _$this._list = list;
ExpenseStateBuilder();
ExpenseStateBuilder get _$this {
if (_$v != null) {
_lastUpdated = _$v.lastUpdated;
_map = _$v.map?.toBuilder();
_list = _$v.list?.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(ExpenseState other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$ExpenseState;
}
@override
void update(void updates(ExpenseStateBuilder b)) {
if (updates != null) updates(this);
}
@override
_$ExpenseState build() {
_$ExpenseState _$result;
try {
_$result = _$v ??
new _$ExpenseState._(
lastUpdated: lastUpdated, map: map.build(), list: list.build());
} catch (_) {
String _$failedField;
try {
_$failedField = 'map';
map.build();
_$failedField = 'list';
list.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
'ExpenseState', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
}
class _$ExpenseUIState extends ExpenseUIState {
@override
final ExpenseEntity editing;
@override
final int selectedId;
@override
final ListUIState listUIState;
factory _$ExpenseUIState([void updates(ExpenseUIStateBuilder b)]) =>
(new ExpenseUIStateBuilder()..update(updates)).build();
_$ExpenseUIState._({this.editing, this.selectedId, this.listUIState})
: super._() {
if (selectedId == null) {
throw new BuiltValueNullFieldError('ExpenseUIState', 'selectedId');
}
if (listUIState == null) {
throw new BuiltValueNullFieldError('ExpenseUIState', 'listUIState');
}
}
@override
ExpenseUIState rebuild(void updates(ExpenseUIStateBuilder b)) =>
(toBuilder()..update(updates)).build();
@override
ExpenseUIStateBuilder toBuilder() =>
new ExpenseUIStateBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is ExpenseUIState &&
editing == other.editing &&
selectedId == other.selectedId &&
listUIState == other.listUIState;
}
@override
int get hashCode {
return $jf($jc($jc($jc(0, editing.hashCode), selectedId.hashCode),
listUIState.hashCode));
}
@override
String toString() {
return (newBuiltValueToStringHelper('ExpenseUIState')
..add('editing', editing)
..add('selectedId', selectedId)
..add('listUIState', listUIState))
.toString();
}
}
class ExpenseUIStateBuilder
implements Builder<ExpenseUIState, ExpenseUIStateBuilder> {
_$ExpenseUIState _$v;
ExpenseEntityBuilder _editing;
ExpenseEntityBuilder get editing =>
_$this._editing ??= new ExpenseEntityBuilder();
set editing(ExpenseEntityBuilder editing) => _$this._editing = editing;
int _selectedId;
int get selectedId => _$this._selectedId;
set selectedId(int selectedId) => _$this._selectedId = selectedId;
ListUIStateBuilder _listUIState;
ListUIStateBuilder get listUIState =>
_$this._listUIState ??= new ListUIStateBuilder();
set listUIState(ListUIStateBuilder listUIState) =>
_$this._listUIState = listUIState;
ExpenseUIStateBuilder();
ExpenseUIStateBuilder get _$this {
if (_$v != null) {
_editing = _$v.editing?.toBuilder();
_selectedId = _$v.selectedId;
_listUIState = _$v.listUIState?.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(ExpenseUIState other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$ExpenseUIState;
}
@override
void update(void updates(ExpenseUIStateBuilder b)) {
if (updates != null) updates(this);
}
@override
_$ExpenseUIState build() {
_$ExpenseUIState _$result;
try {
_$result = _$v ??
new _$ExpenseUIState._(
editing: _editing?.build(),
selectedId: selectedId,
listUIState: listUIState.build());
} catch (_) {
String _$failedField;
try {
_$failedField = 'editing';
_editing?.build();
_$failedField = 'listUIState';
listUIState.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
'ExpenseUIState', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
}

View File

@ -9,6 +9,8 @@ import 'package:invoiceninja_flutter/redux/invoice/invoice_reducer.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/expense/expense_reducer.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_reducer.dart'; import 'package:invoiceninja_flutter/redux/vendor/vendor_reducer.dart';
import 'package:invoiceninja_flutter/redux/task/task_reducer.dart'; import 'package:invoiceninja_flutter/redux/task/task_reducer.dart';
@ -36,6 +38,7 @@ UIState uiReducer(UIState state, dynamic action) {
..dashboardUIState ..dashboardUIState
.replace(dashboardUIReducer(state.dashboardUIState, action)) .replace(dashboardUIReducer(state.dashboardUIState, action))
// STARTER: reducer - do not remove comment // STARTER: reducer - do not remove comment
..expenseUIState.replace(expenseUIReducer(state.expenseUIState, action))
..vendorUIState.replace(vendorUIReducer(state.vendorUIState, action)) ..vendorUIState.replace(vendorUIReducer(state.vendorUIState, action))
..taskUIState.replace(taskUIReducer(state.taskUIState, action)) ..taskUIState.replace(taskUIReducer(state.taskUIState, action))
..projectUIState.replace(projectUIReducer(state.projectUIState, action)) ..projectUIState.replace(projectUIReducer(state.projectUIState, action))

View File

@ -8,6 +8,8 @@ import 'package:invoiceninja_flutter/redux/product/product_state.dart';
import 'package:invoiceninja_flutter/ui/auth/login_vm.dart'; import 'package:invoiceninja_flutter/ui/auth/login_vm.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/expense/expense_state.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart'; import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart';
import 'package:invoiceninja_flutter/redux/task/task_state.dart'; import 'package:invoiceninja_flutter/redux/task/task_state.dart';
@ -35,6 +37,8 @@ abstract class UIState implements Built<UIState, UIStateBuilder> {
clientUIState: ClientUIState(), clientUIState: ClientUIState(),
invoiceUIState: InvoiceUIState(), invoiceUIState: InvoiceUIState(),
// STARTER: constructor - do not remove comment // STARTER: constructor - do not remove comment
expenseUIState: ExpenseUIState(),
vendorUIState: VendorUIState(), vendorUIState: VendorUIState(),
taskUIState: TaskUIState(), taskUIState: TaskUIState(),
@ -70,6 +74,8 @@ abstract class UIState implements Built<UIState, UIStateBuilder> {
String get filter; String get filter;
// STARTER: properties - do not remove comment // STARTER: properties - do not remove comment
ExpenseUIState get expenseUIState;
VendorUIState get vendorUIState; VendorUIState get vendorUIState;
TaskUIState get taskUIState; TaskUIState get taskUIState;

View File

@ -61,6 +61,9 @@ class _$UIStateSerializer implements StructuredSerializer<UIState> {
'invoiceUIState', 'invoiceUIState',
serializers.serialize(object.invoiceUIState, serializers.serialize(object.invoiceUIState,
specifiedType: const FullType(InvoiceUIState)), specifiedType: const FullType(InvoiceUIState)),
'expenseUIState',
serializers.serialize(object.expenseUIState,
specifiedType: const FullType(ExpenseUIState)),
'vendorUIState', 'vendorUIState',
serializers.serialize(object.vendorUIState, serializers.serialize(object.vendorUIState,
specifiedType: const FullType(VendorUIState)), specifiedType: const FullType(VendorUIState)),
@ -143,6 +146,10 @@ class _$UIStateSerializer implements StructuredSerializer<UIState> {
result.filter = serializers.deserialize(value, result.filter = serializers.deserialize(value,
specifiedType: const FullType(String)) as String; specifiedType: const FullType(String)) as String;
break; break;
case 'expenseUIState':
result.expenseUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(ExpenseUIState)) as ExpenseUIState);
break;
case 'vendorUIState': case 'vendorUIState':
result.vendorUIState.replace(serializers.deserialize(value, result.vendorUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(VendorUIState)) as VendorUIState); specifiedType: const FullType(VendorUIState)) as VendorUIState);
@ -194,6 +201,8 @@ class _$UIState extends UIState {
@override @override
final String filter; final String filter;
@override @override
final ExpenseUIState expenseUIState;
@override
final VendorUIState vendorUIState; final VendorUIState vendorUIState;
@override @override
final TaskUIState taskUIState; final TaskUIState taskUIState;
@ -219,6 +228,7 @@ class _$UIState extends UIState {
this.clientUIState, this.clientUIState,
this.invoiceUIState, this.invoiceUIState,
this.filter, this.filter,
this.expenseUIState,
this.vendorUIState, this.vendorUIState,
this.taskUIState, this.taskUIState,
this.projectUIState, this.projectUIState,
@ -255,6 +265,9 @@ class _$UIState extends UIState {
if (invoiceUIState == null) { if (invoiceUIState == null) {
throw new BuiltValueNullFieldError('UIState', 'invoiceUIState'); throw new BuiltValueNullFieldError('UIState', 'invoiceUIState');
} }
if (expenseUIState == null) {
throw new BuiltValueNullFieldError('UIState', 'expenseUIState');
}
if (vendorUIState == null) { if (vendorUIState == null) {
throw new BuiltValueNullFieldError('UIState', 'vendorUIState'); throw new BuiltValueNullFieldError('UIState', 'vendorUIState');
} }
@ -294,6 +307,7 @@ class _$UIState extends UIState {
clientUIState == other.clientUIState && clientUIState == other.clientUIState &&
invoiceUIState == other.invoiceUIState && invoiceUIState == other.invoiceUIState &&
filter == other.filter && filter == other.filter &&
expenseUIState == other.expenseUIState &&
vendorUIState == other.vendorUIState && vendorUIState == other.vendorUIState &&
taskUIState == other.taskUIState && taskUIState == other.taskUIState &&
projectUIState == other.projectUIState && projectUIState == other.projectUIState &&
@ -319,22 +333,24 @@ class _$UIState extends UIState {
$jc( $jc(
$jc( $jc(
$jc( $jc(
0, $jc(
selectedCompanyIndex 0,
selectedCompanyIndex
.hashCode),
currentRoute
.hashCode), .hashCode),
currentRoute enableDarkMode
.hashCode), .hashCode),
enableDarkMode requireAuthentication
.hashCode), .hashCode),
requireAuthentication emailPayment.hashCode),
.hashCode), autoStartTasks.hashCode),
emailPayment.hashCode), dashboardUIState.hashCode),
autoStartTasks.hashCode), productUIState.hashCode),
dashboardUIState.hashCode), clientUIState.hashCode),
productUIState.hashCode), invoiceUIState.hashCode),
clientUIState.hashCode), filter.hashCode),
invoiceUIState.hashCode), expenseUIState.hashCode),
filter.hashCode),
vendorUIState.hashCode), vendorUIState.hashCode),
taskUIState.hashCode), taskUIState.hashCode),
projectUIState.hashCode), projectUIState.hashCode),
@ -356,6 +372,7 @@ class _$UIState extends UIState {
..add('clientUIState', clientUIState) ..add('clientUIState', clientUIState)
..add('invoiceUIState', invoiceUIState) ..add('invoiceUIState', invoiceUIState)
..add('filter', filter) ..add('filter', filter)
..add('expenseUIState', expenseUIState)
..add('vendorUIState', vendorUIState) ..add('vendorUIState', vendorUIState)
..add('taskUIState', taskUIState) ..add('taskUIState', taskUIState)
..add('projectUIState', projectUIState) ..add('projectUIState', projectUIState)
@ -424,6 +441,12 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
String get filter => _$this._filter; String get filter => _$this._filter;
set filter(String filter) => _$this._filter = filter; set filter(String filter) => _$this._filter = filter;
ExpenseUIStateBuilder _expenseUIState;
ExpenseUIStateBuilder get expenseUIState =>
_$this._expenseUIState ??= new ExpenseUIStateBuilder();
set expenseUIState(ExpenseUIStateBuilder expenseUIState) =>
_$this._expenseUIState = expenseUIState;
VendorUIStateBuilder _vendorUIState; VendorUIStateBuilder _vendorUIState;
VendorUIStateBuilder get vendorUIState => VendorUIStateBuilder get vendorUIState =>
_$this._vendorUIState ??= new VendorUIStateBuilder(); _$this._vendorUIState ??= new VendorUIStateBuilder();
@ -469,6 +492,7 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
_clientUIState = _$v.clientUIState?.toBuilder(); _clientUIState = _$v.clientUIState?.toBuilder();
_invoiceUIState = _$v.invoiceUIState?.toBuilder(); _invoiceUIState = _$v.invoiceUIState?.toBuilder();
_filter = _$v.filter; _filter = _$v.filter;
_expenseUIState = _$v.expenseUIState?.toBuilder();
_vendorUIState = _$v.vendorUIState?.toBuilder(); _vendorUIState = _$v.vendorUIState?.toBuilder();
_taskUIState = _$v.taskUIState?.toBuilder(); _taskUIState = _$v.taskUIState?.toBuilder();
_projectUIState = _$v.projectUIState?.toBuilder(); _projectUIState = _$v.projectUIState?.toBuilder();
@ -509,6 +533,7 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
clientUIState: clientUIState.build(), clientUIState: clientUIState.build(),
invoiceUIState: invoiceUIState.build(), invoiceUIState: invoiceUIState.build(),
filter: filter, filter: filter,
expenseUIState: expenseUIState.build(),
vendorUIState: vendorUIState.build(), vendorUIState: vendorUIState.build(),
taskUIState: taskUIState.build(), taskUIState: taskUIState.build(),
projectUIState: projectUIState.build(), projectUIState: projectUIState.build(),
@ -526,6 +551,8 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
_$failedField = 'invoiceUIState'; _$failedField = 'invoiceUIState';
invoiceUIState.build(); invoiceUIState.build();
_$failedField = 'expenseUIState';
expenseUIState.build();
_$failedField = 'vendorUIState'; _$failedField = 'vendorUIState';
vendorUIState.build(); vendorUIState.build();
_$failedField = 'taskUIState'; _$failedField = 'taskUIState';

View File

@ -20,7 +20,8 @@ final editingVendorContactReducer = combineReducers<VendorContactEntity>([
TypedReducer<VendorContactEntity, EditVendorContact>(editVendorContact), TypedReducer<VendorContactEntity, EditVendorContact>(editVendorContact),
]); ]);
VendorContactEntity editVendorContact(VendorContactEntity contact, dynamic action) { VendorContactEntity editVendorContact(
VendorContactEntity contact, dynamic action) {
return action.contact ?? VendorContactEntity(); return action.contact ?? VendorContactEntity();
} }

View File

@ -19,6 +19,8 @@ import 'package:redux/redux.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/expense/expense_actions.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart'; import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart';
import 'package:invoiceninja_flutter/redux/task/task_actions.dart'; import 'package:invoiceninja_flutter/redux/task/task_actions.dart';
@ -253,6 +255,19 @@ class AppDrawer extends StatelessWidget {
}, },
), ),
// STARTER: menu - do not remove comment // STARTER: menu - do not remove comment
DrawerTile(
company: company,
entityType: EntityType.expense,
icon: getEntityIcon(EntityType.expense),
title: localization.expenses,
onTap: () => store.dispatch(ViewExpenseList(context)),
onCreateTap: () {
navigator.pop();
store.dispatch(
EditExpense(expense: ExpenseEntity(), context: context));
},
),
DrawerTile( DrawerTile(
company: company, company: company,
icon: FontAwesomeIcons.cog, icon: FontAwesomeIcons.cog,

View File

@ -0,0 +1,110 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/expense/edit/expense_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/app/buttons/action_icon_button.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class ExpenseEdit extends StatefulWidget {
const ExpenseEdit({
Key key,
@required this.viewModel,
}) : super(key: key);
final ExpenseEditVM viewModel;
@override
_ExpenseEditState createState() => _ExpenseEditState();
}
class _ExpenseEditState extends State<ExpenseEdit> {
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
// STARTER: controllers - do not remove comment
List<TextEditingController> _controllers = [];
@override
void didChangeDependencies() {
_controllers = [
// STARTER: array - do not remove comment
];
_controllers.forEach((controller) => controller.removeListener(_onChanged));
final expense = widget.viewModel.expense;
// STARTER: read value - do not remove comment
_controllers.forEach((controller) => controller.addListener(_onChanged));
super.didChangeDependencies();
}
@override
void dispose() {
_controllers.forEach((controller) {
controller.removeListener(_onChanged);
controller.dispose();
});
super.dispose();
}
void _onChanged() {
final expense = widget.viewModel.expense.rebuild((b) => b
// STARTER: set value - do not remove comment
);
if (expense != widget.viewModel.expense) {
widget.viewModel.onChanged(expense);
}
}
@override
Widget build(BuildContext context) {
final viewModel = widget.viewModel;
final localization = AppLocalization.of(context);
final expense = viewModel.expense;
return WillPopScope(
onWillPop: () async {
viewModel.onBackPressed();
return true;
},
child: Scaffold(
appBar: AppBar(
title: Text(viewModel.expense.isNew
? localization.newExpense
: localization.editExpense),
actions: <Widget>[
ActionIconButton(
icon: Icons.cloud_upload,
tooltip: localization.save,
isVisible: !expense.isDeleted,
isDirty: expense.isNew || expense != viewModel.origExpense,
isSaving: viewModel.isSaving,
onPressed: () {
if (!_formKey.currentState.validate()) {
return;
}
viewModel.onSavePressed(context);
},
),
],
),
body: Form(
key: _formKey,
child: Builder(builder: (BuildContext context) {
return ListView(
children: <Widget>[
FormCard(
children: <Widget>[
// STARTER: widgets - do not remove comment
],
),
],
);
})),
),
);
}
}

View File

@ -0,0 +1,103 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/expense/expense_screen.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart';
import 'package:invoiceninja_flutter/ui/expense/view/expense_view_vm.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_actions.dart';
import 'package:invoiceninja_flutter/data/models/expense_model.dart';
import 'package:invoiceninja_flutter/ui/expense/edit/expense_edit.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class ExpenseEditScreen extends StatelessWidget {
const ExpenseEditScreen({Key key}) : super(key: key);
static const String route = '/expense/edit';
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, ExpenseEditVM>(
converter: (Store<AppState> store) {
return ExpenseEditVM.fromStore(store);
},
builder: (context, viewModel) {
return ExpenseEdit(
viewModel: viewModel,
);
},
);
}
}
class ExpenseEditVM {
ExpenseEditVM({
@required this.state,
@required this.expense,
@required this.company,
@required this.onChanged,
@required this.isSaving,
@required this.origExpense,
@required this.onSavePressed,
@required this.onBackPressed,
@required this.isLoading,
});
factory ExpenseEditVM.fromStore(Store<AppState> store) {
final expense = store.state.expenseUIState.editing;
final state = store.state;
return ExpenseEditVM(
state: state,
isLoading: state.isLoading,
isSaving: state.isSaving,
origExpense: state.expenseState.map[expense.id],
expense: expense,
company: state.selectedCompany,
onChanged: (ExpenseEntity expense) {
store.dispatch(UpdateExpense(expense));
},
onBackPressed: () {
if (state.uiState.currentRoute.contains(ExpenseScreen.route)) {
store.dispatch(UpdateCurrentRoute(
expense.isNew ? ExpenseScreen.route : ExpenseViewScreen.route));
}
},
onSavePressed: (BuildContext context) {
final Completer<ExpenseEntity> completer =
new Completer<ExpenseEntity>();
store.dispatch(
SaveExpenseRequest(completer: completer, expense: expense));
return completer.future.then((_) {
return completer.future.then((savedExpense) {
store.dispatch(UpdateCurrentRoute(ExpenseViewScreen.route));
if (expense.isNew) {
Navigator.of(context)
.pushReplacementNamed(ExpenseViewScreen.route);
} else {
Navigator.of(context).pop(savedExpense);
}
}).catchError((Object error) {
showDialog<ErrorDialog>(
context: context,
builder: (BuildContext context) {
return ErrorDialog(error);
});
});
});
},
);
}
final ExpenseEntity expense;
final CompanyEntity company;
final Function(ExpenseEntity) onChanged;
final Function(BuildContext) onSavePressed;
final Function onBackPressed;
final bool isLoading;
final bool isSaving;
final ExpenseEntity origExpense;
final AppState state;
}

View File

@ -0,0 +1,211 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
import 'package:invoiceninja_flutter/ui/expense/expense_list_item.dart';
import 'package:invoiceninja_flutter/ui/expense/expense_list_vm.dart';
import 'package:invoiceninja_flutter/utils/icons.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class ExpenseList extends StatelessWidget {
const ExpenseList({
Key key,
@required this.viewModel,
}) : super(key: key);
final ExpenseListVM viewModel;
void _showMenu(
BuildContext context, ExpenseEntity expense, ClientEntity client) async {
if (expense == null || client == null) {
return;
}
final user = viewModel.user;
final message = await showDialog<String>(
context: context,
builder: (BuildContext dialogContext) => SimpleDialog(
children: expense
.getEntityActions(
user: user, client: client, includeEdit: true)
.map((entityAction) {
if (entityAction == null) {
return Divider();
} else {
return ListTile(
leading: Icon(getEntityActionIcon(entityAction)),
title: Text(AppLocalization.of(context)
.lookup(entityAction.toString())),
onTap: () {
Navigator.of(dialogContext).pop();
viewModel.onEntityAction(context, expense, entityAction);
},
);
}
}).toList()));
if (message != null) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: message,
)));
}
}
@override
Widget build(BuildContext context) {
/*
final localization = AppLocalization.of(context);
final listState = viewModel.listState;
final filteredClientId = listState.filterEntityId;
final filteredClient =
filteredClientId != null ? viewModel.clientMap[filteredClientId] : null;
*/
return Column(
children: <Widget>[
Expanded(
child: !viewModel.isLoaded
? LoadingIndicator()
: RefreshIndicator(
onRefresh: () => viewModel.onRefreshed(context),
child: viewModel.expenseList.isEmpty
? Opacity(
opacity: 0.5,
child: Center(
child: Text(
AppLocalization.of(context).noRecordsFound,
style: TextStyle(
fontSize: 18.0,
),
),
),
)
: ListView.builder(
shrinkWrap: true,
itemCount: viewModel.expenseList.length,
itemBuilder: (BuildContext context, index) {
final expenseId = viewModel.expenseList[index];
final expense = viewModel.expenseMap[expenseId];
return Column(
children: <Widget>[
ExpenseListItem(
user: viewModel.user,
filter: viewModel.filter,
expense: expense,
onTap: () =>
viewModel.onExpenseTap(context, expense),
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
_showMenu(context, expense, null);
} else {
viewModel.onEntityAction(
context, expense, action);
}
},
onLongPress: () =>
_showMenu(context, expense, null),
),
Divider(
height: 1.0,
),
],
);
},
),
),
),
/*
filteredClient != null
? Material(
color: Colors.orangeAccent,
elevation: 6.0,
child: InkWell(
onTap: () => viewModel.onViewEntityFilterPressed(context),
child: Row(
children: <Widget>[
SizedBox(width: 18.0),
Expanded(
child: Text(
'${localization.filteredBy} ${filteredClient.listDisplayName}',
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
),
),
),
IconButton(
icon: Icon(
Icons.close,
color: Colors.white,
),
onPressed: () => viewModel.onClearEntityFilterPressed(),
)
],
),
),
)
: Container(),
Expanded(
child: !viewModel.isLoaded
? LoadingIndicator()
: RefreshIndicator(
onRefresh: () => viewModel.onRefreshed(context),
child: viewModel.expenseList.isEmpty
? Opacity(
opacity: 0.5,
child: Center(
child: Text(
AppLocalization.of(context).noRecordsFound,
style: TextStyle(
fontSize: 18.0,
),
),
),
)
: ListView.builder(
shrinkWrap: true,
itemCount: viewModel.expenseList.length,
itemBuilder: (BuildContext context, index) {
final expenseId = viewModel.expenseList[index];
final expense = viewModel.expenseMap[expenseId];
final client =
viewModel.clientMap[expense.clientId] ??
ClientEntity();
return Column(
children: <Widget>[
ExpenseListItem(
user: viewModel.user,
filter: viewModel.filter,
expense: expense,
client:
viewModel.clientMap[expense.clientId] ??
ClientEntity(),
onTap: () =>
viewModel.onExpenseTap(context, expense),
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
_showMenu(context, expense, client);
} else {
viewModel.onEntityAction(
context, expense, action);
}
},
onLongPress: () =>
_showMenu(context, expense, client),
),
Divider(
height: 1.0,
),
],
);
},
),
),
),*/
],
);
}
}

View File

@ -0,0 +1,85 @@
import 'package:invoiceninja_flutter/ui/app/entity_state_label.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/dismissible_entity.dart';
class ExpenseListItem extends StatelessWidget {
const ExpenseListItem({
@required this.user,
@required this.onEntityAction,
@required this.onTap,
@required this.onLongPress,
//@required this.onCheckboxChanged,
@required this.expense,
@required this.filter,
});
final UserEntity user;
final Function(EntityAction) onEntityAction;
final GestureTapCallback onTap;
final GestureTapCallback onLongPress;
//final ValueChanged<bool> onCheckboxChanged;
final ExpenseEntity expense;
final String filter;
static final expenseItemKey = (int id) => Key('__expense_item_${id}__');
@override
Widget build(BuildContext context) {
final filterMatch = filter != null && filter.isNotEmpty
? expense.matchesFilterValue(filter)
: null;
final subtitle = filterMatch;
return DismissibleEntity(
user: user,
entity: expense,
onEntityAction: onEntityAction,
child: ListTile(
onTap: onTap,
onLongPress: onLongPress,
/*
leading: Checkbox(
//key: NinjaKeys.expenseItemCheckbox(expense.id),
value: true,
//onChanged: onCheckboxChanged,
onChanged: (value) {
return true;
},
),
*/
title: Container(
width: MediaQuery.of(context).size.width,
child: Row(
children: <Widget>[
Expanded(
child: Text(
expense.expenseDate,
//key: NinjaKeys.clientItemClientKey(client.id),
style: Theme.of(context).textTheme.title,
),
),
Text(formatNumber(expense.listDisplayAmount, context),
style: Theme.of(context).textTheme.title),
],
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
subtitle != null && subtitle.isNotEmpty
? Text(
subtitle,
maxLines: 3,
overflow: TextOverflow.ellipsis,
)
: Container(),
EntityStateLabel(expense),
],
),
),
);
}
}

View File

@ -0,0 +1,127 @@
import 'dart:async';
import 'package:redux/redux.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_selectors.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/expense/expense_list.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_actions.dart';
class ExpenseListBuilder extends StatelessWidget {
const ExpenseListBuilder({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, ExpenseListVM>(
converter: ExpenseListVM.fromStore,
builder: (context, viewModel) {
return ExpenseList(
viewModel: viewModel,
);
},
);
}
}
class ExpenseListVM {
ExpenseListVM({
@required this.user,
@required this.expenseList,
@required this.expenseMap,
@required this.filter,
@required this.isLoading,
@required this.isLoaded,
@required this.onExpenseTap,
@required this.listState,
@required this.onRefreshed,
@required this.onEntityAction,
@required this.onClearEntityFilterPressed,
@required this.onViewEntityFilterPressed,
});
static ExpenseListVM fromStore(Store<AppState> store) {
Future<Null> _handleRefresh(BuildContext context) {
if (store.state.isLoading) {
return Future<Null>(null);
}
final completer = snackBarCompleter(
context, AppLocalization.of(context).refreshComplete);
store.dispatch(LoadExpenses(completer: completer, force: true));
return completer.future;
}
final state = store.state;
return ExpenseListVM(
user: state.user,
listState: state.expenseListState,
expenseList: memoizedFilteredExpenseList(state.expenseState.map,
state.expenseState.list, state.expenseListState),
expenseMap: state.expenseState.map,
isLoading: state.isLoading,
isLoaded: state.expenseState.isLoaded,
filter: state.expenseUIState.listUIState.filter,
onClearEntityFilterPressed: () =>
store.dispatch(FilterExpensesByEntity()),
onViewEntityFilterPressed: (BuildContext context) => store.dispatch(
ViewClient(
clientId: state.expenseListState.filterEntityId,
context: context)),
onExpenseTap: (context, expense) {
store.dispatch(ViewExpense(expenseId: expense.id, context: context));
},
onEntityAction: (context, expense, action) {
switch (action) {
case EntityAction.edit:
store.dispatch(EditExpense(context: context, expense: expense));
break;
case EntityAction.clone:
Navigator.of(context).pop();
store.dispatch(
EditExpense(context: context, expense: expense.clone));
break;
case EntityAction.restore:
store.dispatch(RestoreExpenseRequest(
snackBarCompleter(
context, AppLocalization.of(context).restoredExpense),
expense.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveExpenseRequest(
snackBarCompleter(
context, AppLocalization.of(context).archivedExpense),
expense.id));
break;
case EntityAction.delete:
store.dispatch(DeleteExpenseRequest(
snackBarCompleter(
context, AppLocalization.of(context).deletedExpense),
expense.id));
break;
}
},
onRefreshed: (context) => _handleRefresh(context),
);
}
final UserEntity user;
final List<int> expenseList;
final BuiltMap<int, ExpenseEntity> expenseMap;
final ListUIState listState;
final String filter;
final bool isLoading;
final bool isLoaded;
final Function(BuildContext, ExpenseEntity) onExpenseTap;
final Function(BuildContext) onRefreshed;
final Function(BuildContext, ExpenseEntity, EntityAction) onEntityAction;
final Function onClearEntityFilterPressed;
final Function(BuildContext) onViewEntityFilterPressed;
}

View File

@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/dashboard/dashboard_actions.dart';
import 'package:invoiceninja_flutter/ui/app/list_filter.dart';
import 'package:invoiceninja_flutter/ui/app/list_filter_button.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/expense/expense_list_vm.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_actions.dart';
import 'package:invoiceninja_flutter/ui/app/app_drawer_vm.dart';
import 'package:invoiceninja_flutter/ui/app/app_bottom_bar.dart';
class ExpenseScreen extends StatelessWidget {
static const String route = '/expense';
@override
Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context);
final company = store.state.selectedCompany;
final user = company.user;
final localization = AppLocalization.of(context);
return WillPopScope(
onWillPop: () async {
store.dispatch(ViewDashboard(context));
return false;
},
child: Scaffold(
appBar: AppBar(
title: ListFilter(
entityType: EntityType.expense,
onFilterChanged: (value) {
store.dispatch(FilterExpenses(value));
},
),
actions: [
ListFilterButton(
entityType: EntityType.expense,
onFilterPressed: (String value) {
store.dispatch(FilterExpenses(value));
},
),
],
),
drawer: AppDrawerBuilder(),
body: ExpenseListBuilder(),
bottomNavigationBar: AppBottomBar(
entityType: EntityType.expense,
onSelectedSortField: (value) => store.dispatch(SortExpenses(value)),
customValues1: company.getCustomFieldValues(CustomFieldType.expense1,
excludeBlank: true),
customValues2: company.getCustomFieldValues(CustomFieldType.expense2,
excludeBlank: true),
onSelectedCustom1: (value) =>
store.dispatch(FilterExpensesByCustom1(value)),
onSelectedCustom2: (value) =>
store.dispatch(FilterExpensesByCustom2(value)),
sortFields: [
ExpenseFields.updatedAt,
],
onSelectedState: (EntityState state, value) {
store.dispatch(FilterExpensesByState(state));
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
floatingActionButton: user.canCreate(EntityType.expense)
? FloatingActionButton(
//key: Key(ExpenseKeys.expenseScreenFABKeyString),
backgroundColor: Theme.of(context).primaryColorDark,
onPressed: () {
store.dispatch(
EditExpense(expense: ExpenseEntity(), context: context));
},
child: Icon(
Icons.add,
color: Colors.white,
),
tooltip: localization.newExpense,
)
: null,
),
);
}
}

View File

@ -0,0 +1,50 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/actions_menu_button.dart';
import 'package:invoiceninja_flutter/ui/expense/view/expense_view_vm.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
class ExpenseView extends StatefulWidget {
const ExpenseView({
Key key,
@required this.viewModel,
}) : super(key: key);
final ExpenseViewVM viewModel;
@override
_ExpenseViewState createState() => new _ExpenseViewState();
}
class _ExpenseViewState extends State<ExpenseView> {
@override
Widget build(BuildContext context) {
final viewModel = widget.viewModel;
final expense = viewModel.expense;
return Scaffold(
appBar: AppBar(
title: Text(expense.expenseDate),
actions: expense.isNew
? []
: [
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
viewModel.onEditPressed(context);
},
),
ActionMenuButton(
user: viewModel.company.user,
isSaving: viewModel.isSaving,
entity: expense,
onSelected: viewModel.onActionSelected,
),
],
),
body: FormCard(children: [
// STARTER: widgets - do not remove comment
]),
);
}
}

View File

@ -0,0 +1,117 @@
import 'dart:async';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/ui/expense/expense_screen.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:redux/redux.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_actions.dart';
import 'package:invoiceninja_flutter/data/models/expense_model.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/expense/view/expense_view.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class ExpenseViewScreen extends StatelessWidget {
const ExpenseViewScreen({Key key}) : super(key: key);
static const String route = '/expense/view';
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, ExpenseViewVM>(
converter: (Store<AppState> store) {
return ExpenseViewVM.fromStore(store);
},
builder: (context, vm) {
return ExpenseView(
viewModel: vm,
);
},
);
}
}
class ExpenseViewVM {
ExpenseViewVM({
@required this.state,
@required this.expense,
@required this.company,
@required this.onActionSelected,
@required this.onEditPressed,
@required this.onBackPressed,
@required this.onRefreshed,
@required this.isSaving,
@required this.isLoading,
@required this.isDirty,
});
factory ExpenseViewVM.fromStore(Store<AppState> store) {
final state = store.state;
final expense = state.expenseState.map[state.expenseUIState.selectedId];
Future<Null> _handleRefresh(BuildContext context) {
final completer = snackBarCompleter(
context, AppLocalization.of(context).refreshComplete);
store.dispatch(LoadExpense(completer: completer, expenseId: expense.id));
return completer.future;
}
return ExpenseViewVM(
state: state,
company: state.selectedCompany,
isSaving: state.isSaving,
isLoading: state.isLoading,
isDirty: expense.isNew,
expense: expense,
onEditPressed: (BuildContext context) {
final Completer<ExpenseEntity> completer = Completer<ExpenseEntity>();
store.dispatch(EditExpense(
expense: expense, context: context, completer: completer));
completer.future.then((expense) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: AppLocalization.of(context).updatedExpense,
)));
});
},
onRefreshed: (context) => _handleRefresh(context),
onBackPressed: () {
if (state.uiState.currentRoute.contains(ExpenseScreen.route)) {
store.dispatch(UpdateCurrentRoute(ExpenseScreen.route));
}
},
onActionSelected: (BuildContext context, EntityAction action) {
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.archive:
store.dispatch(ArchiveExpenseRequest(
popCompleter(context, localization.archivedExpense),
expense.id));
break;
case EntityAction.delete:
store.dispatch(DeleteExpenseRequest(
popCompleter(context, localization.deletedExpense),
expense.id));
break;
case EntityAction.restore:
store.dispatch(RestoreExpenseRequest(
snackBarCompleter(context, localization.restoredExpense),
expense.id));
break;
}
});
}
final AppState state;
final ExpenseEntity expense;
final CompanyEntity company;
final Function(BuildContext, EntityAction) onActionSelected;
final Function(BuildContext) onEditPressed;
final Function onBackPressed;
final Function(BuildContext) onRefreshed;
final bool isSaving;
final bool isLoading;
final bool isDirty;
}

View File

@ -16,8 +16,7 @@ class VendorEditAddress extends StatefulWidget {
final VendorEditVM viewModel; final VendorEditVM viewModel;
@override @override
VendorEditAddressState createState() => VendorEditAddressState createState() => VendorEditAddressState();
VendorEditAddressState();
} }
class VendorEditAddressState extends State<VendorEditAddress> { class VendorEditAddressState extends State<VendorEditAddress> {
@ -126,7 +125,8 @@ class VendorEditAddressState extends State<VendorEditAddress> {
key: ValueKey(vendor.countryId), key: ValueKey(vendor.countryId),
entityType: EntityType.country, entityType: EntityType.country,
entityMap: viewModel.state.staticState.countryMap, entityMap: viewModel.state.staticState.countryMap,
entityList: memoizedCountryList(viewModel.state.staticState.countryMap), entityList:
memoizedCountryList(viewModel.state.staticState.countryMap),
labelText: localization.country, labelText: localization.country,
initialValue: initialValue:
viewModel.state.staticState.countryMap[vendor.countryId]?.name, viewModel.state.staticState.countryMap[vendor.countryId]?.name,

View File

@ -49,7 +49,8 @@ class VendorEditContactsVM {
store.dispatch(AddVendorContact(contact)); store.dispatch(AddVendorContact(contact));
store.dispatch(EditVendorContact(contact)); store.dispatch(EditVendorContact(contact));
}, },
onRemoveContactPressed: (index) => store.dispatch(DeleteVendorContact(index)), onRemoveContactPressed: (index) =>
store.dispatch(DeleteVendorContact(index)),
onDoneContactPressed: () => store.dispatch(EditVendorContact()), onDoneContactPressed: () => store.dispatch(EditVendorContact()),
onChangedContact: (contact, index) { onChangedContact: (contact, index) {
store.dispatch(UpdateVendorContact(contact: contact, index: index)); store.dispatch(UpdateVendorContact(contact: contact, index: index));

View File

@ -81,7 +81,8 @@ class VendorEditNotesState extends State<VendorEditNotes> {
entityMap: staticState.currencyMap, entityMap: staticState.currencyMap,
entityList: memoizedCurrencyList(staticState.currencyMap), entityList: memoizedCurrencyList(staticState.currencyMap),
labelText: localization.currency, labelText: localization.currency,
initialValue: staticState.currencyMap[viewModel.vendor.currencyId]?.name, initialValue:
staticState.currencyMap[viewModel.vendor.currencyId]?.name,
onSelected: (SelectableEntity currency) => viewModel.onChanged( onSelected: (SelectableEntity currency) => viewModel.onChanged(
viewModel.vendor.rebuild((b) => b..currencyId = currency.id)), viewModel.vendor.rebuild((b) => b..currencyId = currency.id)),
), ),

View File

@ -90,12 +90,12 @@ class StubList extends StatelessWidget {
final stub = viewModel.stubMap[stubId]; final stub = viewModel.stubMap[stubId];
return Column( return Column(
children: <Widget>[ children: <Widget>[
VendorListItem( StubListItem(
user: viewModel.user, user: viewModel.user,
filter: viewModel.filter, filter: viewModel.filter,
stub: stub, stub: stub,
onTap: () => onTap: () =>
viewModel.onVendorTap(context, stub), viewModel.onStubTap(context, stub),
onEntityAction: (EntityAction action) { onEntityAction: (EntityAction action) {
if (action == EntityAction.more) { if (action == EntityAction.more) {
_showMenu(context, stub, null); _showMenu(context, stub, null);