Expenses
This commit is contained in:
parent
7821daf0c4
commit
fdacfa4070
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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>())
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),*/
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue