This commit is contained in:
Hillel Coren 2019-06-02 15:09:05 +03:00
parent bfe4d22b7c
commit b8df45c5a0
28 changed files with 2366 additions and 67 deletions

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter"> <configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter" singleton="false">
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" /> <option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
<method /> <method v="2" />
</configuration> </configuration>
</component> </component>

View File

@ -23,6 +23,8 @@ import 'package:invoiceninja_flutter/redux/ui/ui_state.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_state.dart'; import 'package:invoiceninja_flutter/redux/invoice/invoice_state.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart';
import 'package:invoiceninja_flutter/redux/task/task_state.dart'; import 'package:invoiceninja_flutter/redux/task/task_state.dart';
import 'package:invoiceninja_flutter/redux/project/project_state.dart'; import 'package:invoiceninja_flutter/redux/project/project_state.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_state.dart'; import 'package:invoiceninja_flutter/redux/payment/payment_state.dart';
@ -76,6 +78,8 @@ part 'serializers.g.dart';
TimezoneItemResponse, TimezoneItemResponse,
TimezoneListResponse, TimezoneListResponse,
// STARTER: serializers - do not remove comment // STARTER: serializers - do not remove comment
VendorEntity,
TaskEntity, TaskEntity,
ProjectEntity, ProjectEntity,
PaymentEntity, PaymentEntity,

View File

@ -127,6 +127,8 @@ Serializers _$serializers = (new Serializers().toBuilder()
..add(VendorEntity.serializer) ..add(VendorEntity.serializer)
..add(VendorItemResponse.serializer) ..add(VendorItemResponse.serializer)
..add(VendorListResponse.serializer) ..add(VendorListResponse.serializer)
..add(VendorState.serializer)
..add(VendorUIState.serializer)
..addBuilderFactory( ..addBuilderFactory(
const FullType(BuiltList, const [const FullType(ActivityEntity)]), const FullType(BuiltList, const [const FullType(ActivityEntity)]),
() => new ListBuilder<ActivityEntity>()) () => new ListBuilder<ActivityEntity>())
@ -363,5 +365,7 @@ Serializers _$serializers = (new Serializers().toBuilder()
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(ProjectEntity)]), () => new MapBuilder<int, ProjectEntity>()) ..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(ProjectEntity)]), () => new MapBuilder<int, ProjectEntity>())
..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(TaskEntity)]), () => new MapBuilder<int, TaskEntity>()) ..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(TaskEntity)]), () => new MapBuilder<int, TaskEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(VendorEntity)]), () => new MapBuilder<int, VendorEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>())) ..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>()))
.build(); .build();

View File

@ -0,0 +1,70 @@
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 VendorRepository {
const VendorRepository({
this.webClient = const WebClient(),
});
final WebClient webClient;
Future<VendorEntity> loadItem(
CompanyEntity company, AuthState auth, int entityId) async {
final dynamic response = await webClient.get(
'${auth.url}/vendors/$entityId', company.token);
final VendorItemResponse vendorResponse =
serializers.deserializeWith(VendorItemResponse.serializer, response);
return vendorResponse.data;
}
Future<BuiltList<VendorEntity>> loadList(
CompanyEntity company, AuthState auth, int updatedAt) async {
String url = auth.url + '/vendors?';
if (updatedAt > 0) {
url += '&updated_at=${updatedAt - kUpdatedAtBufferSeconds}';
}
final dynamic response = await webClient.get(url, company.token);
final VendorListResponse vendorResponse =
serializers.deserializeWith(VendorListResponse.serializer, response);
return vendorResponse.data;
}
Future<VendorEntity> saveData(
CompanyEntity company, AuthState auth, VendorEntity vendor,
[EntityAction action]) async {
final data = serializers.serializeWith(VendorEntity.serializer, vendor);
dynamic response;
if (vendor.isNew) {
response = await webClient.post(
auth.url + '/vendors',
company.token,
json.encode(data));
} else {
var url = auth.url + '/vendors/' + vendor.id.toString();
if (action != null) {
url += '?action=' + action.toString();
}
response = await webClient.put(url, company.token, json.encode(data));
}
final VendorItemResponse vendorResponse =
serializers.deserializeWith(VendorItemResponse.serializer, response);
return vendorResponse.data;
}
}

View File

@ -43,6 +43,12 @@ import 'package:local_auth/local_auth.dart';
//import 'package:quick_actions/quick_actions.dart'; //import 'package:quick_actions/quick_actions.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/ui/vendor/vendor_screen.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/redux/vendor/vendor_actions.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_middleware.dart';
import 'package:invoiceninja_flutter/ui/task/task_screen.dart'; import 'package:invoiceninja_flutter/ui/task/task_screen.dart';
import 'package:invoiceninja_flutter/ui/task/edit/task_edit_vm.dart'; import 'package:invoiceninja_flutter/ui/task/edit/task_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/task/view/task_view_vm.dart'; import 'package:invoiceninja_flutter/ui/task/view/task_view_vm.dart';
@ -91,6 +97,8 @@ void main() async {
..addAll(createStoreInvoicesMiddleware()) ..addAll(createStoreInvoicesMiddleware())
..addAll(createStorePersistenceMiddleware()) ..addAll(createStorePersistenceMiddleware())
// STARTER: middleware - do not remove comment // STARTER: middleware - do not remove comment
..addAll(createStoreVendorsMiddleware())
..addAll(createStoreTasksMiddleware()) ..addAll(createStoreTasksMiddleware())
..addAll(createStoreProjectsMiddleware()) ..addAll(createStoreProjectsMiddleware())
..addAll(createStorePaymentsMiddleware()) ..addAll(createStorePaymentsMiddleware())
@ -299,6 +307,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
VendorScreen.route: (context) {
widget.store.dispatch(LoadVendors());
return VendorScreen();
},
VendorViewScreen.route: (context) => VendorViewScreen(),
VendorEditScreen.route: (context) => VendorEditScreen(),
TaskScreen.route: (context) { TaskScreen.route: (context) {
widget.store.dispatch(LoadTasks()); widget.store.dispatch(LoadTasks());
return TaskScreen(); return TaskScreen();

View File

@ -13,6 +13,8 @@ import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart'; import 'package:built_value/serializer.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart';
import 'package:invoiceninja_flutter/redux/task/task_state.dart'; import 'package:invoiceninja_flutter/redux/task/task_state.dart';
import 'package:invoiceninja_flutter/redux/project/project_state.dart'; import 'package:invoiceninja_flutter/redux/project/project_state.dart';
@ -107,6 +109,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.vendor:
return vendorUIState;
case EntityType.task: case EntityType.task:
return taskUIState; return taskUIState;
@ -145,6 +150,11 @@ 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
VendorState get vendorState => selectedCompanyState.vendorState;
ListUIState get vendorListState => uiState.vendorUIState.listUIState;
VendorUIState get vendorUIState => uiState.vendorUIState;
TaskState get taskState => selectedCompanyState.taskState; TaskState get taskState => selectedCompanyState.taskState;
ListUIState get taskListState => uiState.taskUIState.listUIState; ListUIState get taskListState => uiState.taskUIState.listUIState;

View File

@ -9,6 +9,8 @@ import 'package:invoiceninja_flutter/redux/dashboard/dashboard_reducer.dart';
import 'package:invoiceninja_flutter/redux/company/company_actions.dart'; import 'package:invoiceninja_flutter/redux/company/company_actions.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/vendor/vendor_reducer.dart';
import 'package:invoiceninja_flutter/redux/task/task_reducer.dart'; import 'package:invoiceninja_flutter/redux/task/task_reducer.dart';
import 'package:invoiceninja_flutter/redux/project/project_reducer.dart'; import 'package:invoiceninja_flutter/redux/project/project_reducer.dart';
@ -29,6 +31,8 @@ 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
..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))
..paymentState.replace(paymentsReducer(state.paymentState, action)) ..paymentState.replace(paymentsReducer(state.paymentState, action))

View File

@ -7,6 +7,8 @@ import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart'; import 'package:built_value/serializer.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart';
import 'package:invoiceninja_flutter/redux/task/task_state.dart'; import 'package:invoiceninja_flutter/redux/task/task_state.dart';
import 'package:invoiceninja_flutter/redux/project/project_state.dart'; import 'package:invoiceninja_flutter/redux/project/project_state.dart';
@ -27,6 +29,8 @@ abstract class CompanyState
clientState: ClientState(), clientState: ClientState(),
invoiceState: InvoiceState(), invoiceState: InvoiceState(),
// STARTER: constructor - do not remove comment // STARTER: constructor - do not remove comment
vendorState: VendorState(),
taskState: TaskState(), taskState: TaskState(),
projectState: ProjectState(), projectState: ProjectState(),
paymentState: PaymentState(), paymentState: PaymentState(),
@ -48,6 +52,8 @@ abstract class CompanyState
InvoiceState get invoiceState; InvoiceState get invoiceState;
// STARTER: fields - do not remove comment // STARTER: fields - do not remove comment
VendorState get vendorState;
TaskState get taskState; TaskState get taskState;
ProjectState get projectState; ProjectState get projectState;

View File

@ -44,6 +44,9 @@ class _$CompanyStateSerializer implements StructuredSerializer<CompanyState> {
'invoiceState', 'invoiceState',
serializers.serialize(object.invoiceState, serializers.serialize(object.invoiceState,
specifiedType: const FullType(InvoiceState)), specifiedType: const FullType(InvoiceState)),
'vendorState',
serializers.serialize(object.vendorState,
specifiedType: const FullType(VendorState)),
'taskState', 'taskState',
serializers.serialize(object.taskState, serializers.serialize(object.taskState,
specifiedType: const FullType(TaskState)), specifiedType: const FullType(TaskState)),
@ -98,6 +101,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 'vendorState':
result.vendorState.replace(serializers.deserialize(value,
specifiedType: const FullType(VendorState)) as VendorState);
break;
case 'taskState': case 'taskState':
result.taskState.replace(serializers.deserialize(value, result.taskState.replace(serializers.deserialize(value,
specifiedType: const FullType(TaskState)) as TaskState); specifiedType: const FullType(TaskState)) as TaskState);
@ -133,6 +140,8 @@ class _$CompanyState extends CompanyState {
@override @override
final InvoiceState invoiceState; final InvoiceState invoiceState;
@override @override
final VendorState vendorState;
@override
final TaskState taskState; final TaskState taskState;
@override @override
final ProjectState projectState; final ProjectState projectState;
@ -150,6 +159,7 @@ class _$CompanyState extends CompanyState {
this.productState, this.productState,
this.clientState, this.clientState,
this.invoiceState, this.invoiceState,
this.vendorState,
this.taskState, this.taskState,
this.projectState, this.projectState,
this.paymentState, this.paymentState,
@ -167,6 +177,9 @@ class _$CompanyState extends CompanyState {
if (invoiceState == null) { if (invoiceState == null) {
throw new BuiltValueNullFieldError('CompanyState', 'invoiceState'); throw new BuiltValueNullFieldError('CompanyState', 'invoiceState');
} }
if (vendorState == null) {
throw new BuiltValueNullFieldError('CompanyState', 'vendorState');
}
if (taskState == null) { if (taskState == null) {
throw new BuiltValueNullFieldError('CompanyState', 'taskState'); throw new BuiltValueNullFieldError('CompanyState', 'taskState');
} }
@ -197,6 +210,7 @@ class _$CompanyState extends CompanyState {
productState == other.productState && productState == other.productState &&
clientState == other.clientState && clientState == other.clientState &&
invoiceState == other.invoiceState && invoiceState == other.invoiceState &&
vendorState == other.vendorState &&
taskState == other.taskState && taskState == other.taskState &&
projectState == other.projectState && projectState == other.projectState &&
paymentState == other.paymentState && paymentState == other.paymentState &&
@ -212,11 +226,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),
vendorState.hashCode),
taskState.hashCode), taskState.hashCode),
projectState.hashCode), projectState.hashCode),
paymentState.hashCode), paymentState.hashCode),
@ -231,6 +247,7 @@ class _$CompanyState extends CompanyState {
..add('productState', productState) ..add('productState', productState)
..add('clientState', clientState) ..add('clientState', clientState)
..add('invoiceState', invoiceState) ..add('invoiceState', invoiceState)
..add('vendorState', vendorState)
..add('taskState', taskState) ..add('taskState', taskState)
..add('projectState', projectState) ..add('projectState', projectState)
..add('paymentState', paymentState) ..add('paymentState', paymentState)
@ -272,6 +289,12 @@ class CompanyStateBuilder
set invoiceState(InvoiceStateBuilder invoiceState) => set invoiceState(InvoiceStateBuilder invoiceState) =>
_$this._invoiceState = invoiceState; _$this._invoiceState = invoiceState;
VendorStateBuilder _vendorState;
VendorStateBuilder get vendorState =>
_$this._vendorState ??= new VendorStateBuilder();
set vendorState(VendorStateBuilder vendorState) =>
_$this._vendorState = vendorState;
TaskStateBuilder _taskState; TaskStateBuilder _taskState;
TaskStateBuilder get taskState => TaskStateBuilder get taskState =>
_$this._taskState ??= new TaskStateBuilder(); _$this._taskState ??= new TaskStateBuilder();
@ -304,6 +327,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();
_vendorState = _$v.vendorState?.toBuilder();
_taskState = _$v.taskState?.toBuilder(); _taskState = _$v.taskState?.toBuilder();
_projectState = _$v.projectState?.toBuilder(); _projectState = _$v.projectState?.toBuilder();
_paymentState = _$v.paymentState?.toBuilder(); _paymentState = _$v.paymentState?.toBuilder();
@ -337,6 +361,7 @@ class CompanyStateBuilder
productState: productState.build(), productState: productState.build(),
clientState: clientState.build(), clientState: clientState.build(),
invoiceState: invoiceState.build(), invoiceState: invoiceState.build(),
vendorState: vendorState.build(),
taskState: taskState.build(), taskState: taskState.build(),
projectState: projectState.build(), projectState: projectState.build(),
paymentState: paymentState.build(), paymentState: paymentState.build(),
@ -354,6 +379,8 @@ class CompanyStateBuilder
clientState.build(); clientState.build();
_$failedField = 'invoiceState'; _$failedField = 'invoiceState';
invoiceState.build(); invoiceState.build();
_$failedField = 'vendorState';
vendorState.build();
_$failedField = 'taskState'; _$failedField = 'taskState';
taskState.build(); taskState.build();
_$failedField = 'projectState'; _$failedField = 'projectState';

View File

@ -9,6 +9,8 @@ import 'package:invoiceninja_flutter/redux/invoice/invoice_reducer.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/vendor/vendor_reducer.dart';
import 'package:invoiceninja_flutter/redux/task/task_reducer.dart'; import 'package:invoiceninja_flutter/redux/task/task_reducer.dart';
import 'package:invoiceninja_flutter/redux/project/project_reducer.dart'; import 'package:invoiceninja_flutter/redux/project/project_reducer.dart';
@ -34,6 +36,8 @@ 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
..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))
..paymentUIState.replace(paymentUIReducer(state.paymentUIState, action)) ..paymentUIState.replace(paymentUIReducer(state.paymentUIState, action))

View File

@ -8,6 +8,8 @@ import 'package:invoiceninja_flutter/redux/product/product_state.dart';
import 'package:invoiceninja_flutter/ui/auth/login_vm.dart'; import 'package:invoiceninja_flutter/ui/auth/login_vm.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart';
import 'package:invoiceninja_flutter/redux/task/task_state.dart'; import 'package:invoiceninja_flutter/redux/task/task_state.dart';
import 'package:invoiceninja_flutter/redux/project/project_state.dart'; import 'package:invoiceninja_flutter/redux/project/project_state.dart';
@ -33,6 +35,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
vendorUIState: VendorUIState(),
taskUIState: TaskUIState(), taskUIState: TaskUIState(),
projectUIState: ProjectUIState(), projectUIState: ProjectUIState(),
paymentUIState: PaymentUIState(), paymentUIState: PaymentUIState(),
@ -66,6 +70,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
VendorUIState get vendorUIState;
TaskUIState get taskUIState; TaskUIState get taskUIState;
ProjectUIState get projectUIState; ProjectUIState get projectUIState;

View File

@ -61,6 +61,9 @@ class _$UIStateSerializer implements StructuredSerializer<UIState> {
'invoiceUIState', 'invoiceUIState',
serializers.serialize(object.invoiceUIState, serializers.serialize(object.invoiceUIState,
specifiedType: const FullType(InvoiceUIState)), specifiedType: const FullType(InvoiceUIState)),
'vendorUIState',
serializers.serialize(object.vendorUIState,
specifiedType: const FullType(VendorUIState)),
'taskUIState', 'taskUIState',
serializers.serialize(object.taskUIState, serializers.serialize(object.taskUIState,
specifiedType: const FullType(TaskUIState)), specifiedType: const FullType(TaskUIState)),
@ -140,6 +143,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 'vendorUIState':
result.vendorUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(VendorUIState)) as VendorUIState);
break;
case 'taskUIState': case 'taskUIState':
result.taskUIState.replace(serializers.deserialize(value, result.taskUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(TaskUIState)) as TaskUIState); specifiedType: const FullType(TaskUIState)) as TaskUIState);
@ -187,6 +194,8 @@ class _$UIState extends UIState {
@override @override
final String filter; final String filter;
@override @override
final VendorUIState vendorUIState;
@override
final TaskUIState taskUIState; final TaskUIState taskUIState;
@override @override
final ProjectUIState projectUIState; final ProjectUIState projectUIState;
@ -210,6 +219,7 @@ class _$UIState extends UIState {
this.clientUIState, this.clientUIState,
this.invoiceUIState, this.invoiceUIState,
this.filter, this.filter,
this.vendorUIState,
this.taskUIState, this.taskUIState,
this.projectUIState, this.projectUIState,
this.paymentUIState, this.paymentUIState,
@ -245,6 +255,9 @@ class _$UIState extends UIState {
if (invoiceUIState == null) { if (invoiceUIState == null) {
throw new BuiltValueNullFieldError('UIState', 'invoiceUIState'); throw new BuiltValueNullFieldError('UIState', 'invoiceUIState');
} }
if (vendorUIState == null) {
throw new BuiltValueNullFieldError('UIState', 'vendorUIState');
}
if (taskUIState == null) { if (taskUIState == null) {
throw new BuiltValueNullFieldError('UIState', 'taskUIState'); throw new BuiltValueNullFieldError('UIState', 'taskUIState');
} }
@ -281,6 +294,7 @@ class _$UIState extends UIState {
clientUIState == other.clientUIState && clientUIState == other.clientUIState &&
invoiceUIState == other.invoiceUIState && invoiceUIState == other.invoiceUIState &&
filter == other.filter && filter == other.filter &&
vendorUIState == other.vendorUIState &&
taskUIState == other.taskUIState && taskUIState == other.taskUIState &&
projectUIState == other.projectUIState && projectUIState == other.projectUIState &&
paymentUIState == other.paymentUIState && paymentUIState == other.paymentUIState &&
@ -304,22 +318,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), vendorUIState.hashCode),
filter.hashCode),
taskUIState.hashCode), taskUIState.hashCode),
projectUIState.hashCode), projectUIState.hashCode),
paymentUIState.hashCode), paymentUIState.hashCode),
@ -340,6 +356,7 @@ class _$UIState extends UIState {
..add('clientUIState', clientUIState) ..add('clientUIState', clientUIState)
..add('invoiceUIState', invoiceUIState) ..add('invoiceUIState', invoiceUIState)
..add('filter', filter) ..add('filter', filter)
..add('vendorUIState', vendorUIState)
..add('taskUIState', taskUIState) ..add('taskUIState', taskUIState)
..add('projectUIState', projectUIState) ..add('projectUIState', projectUIState)
..add('paymentUIState', paymentUIState) ..add('paymentUIState', paymentUIState)
@ -407,6 +424,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;
VendorUIStateBuilder _vendorUIState;
VendorUIStateBuilder get vendorUIState =>
_$this._vendorUIState ??= new VendorUIStateBuilder();
set vendorUIState(VendorUIStateBuilder vendorUIState) =>
_$this._vendorUIState = vendorUIState;
TaskUIStateBuilder _taskUIState; TaskUIStateBuilder _taskUIState;
TaskUIStateBuilder get taskUIState => TaskUIStateBuilder get taskUIState =>
_$this._taskUIState ??= new TaskUIStateBuilder(); _$this._taskUIState ??= new TaskUIStateBuilder();
@ -446,6 +469,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;
_vendorUIState = _$v.vendorUIState?.toBuilder();
_taskUIState = _$v.taskUIState?.toBuilder(); _taskUIState = _$v.taskUIState?.toBuilder();
_projectUIState = _$v.projectUIState?.toBuilder(); _projectUIState = _$v.projectUIState?.toBuilder();
_paymentUIState = _$v.paymentUIState?.toBuilder(); _paymentUIState = _$v.paymentUIState?.toBuilder();
@ -485,6 +509,7 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
clientUIState: clientUIState.build(), clientUIState: clientUIState.build(),
invoiceUIState: invoiceUIState.build(), invoiceUIState: invoiceUIState.build(),
filter: filter, filter: filter,
vendorUIState: vendorUIState.build(),
taskUIState: taskUIState.build(), taskUIState: taskUIState.build(),
projectUIState: projectUIState.build(), projectUIState: projectUIState.build(),
paymentUIState: paymentUIState.build(), paymentUIState: paymentUIState.build(),
@ -501,6 +526,8 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
_$failedField = 'invoiceUIState'; _$failedField = 'invoiceUIState';
invoiceUIState.build(); invoiceUIState.build();
_$failedField = 'vendorUIState';
vendorUIState.build();
_$failedField = 'taskUIState'; _$failedField = 'taskUIState';
taskUIState.build(); taskUIState.build();
_$failedField = 'projectUIState'; _$failedField = 'projectUIState';

226
lib/redux/vendor/vendor_actions.dart vendored Normal file
View File

@ -0,0 +1,226 @@
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 ViewVendorList implements PersistUI {
ViewVendorList(this.context);
final BuildContext context;
}
class ViewVendor implements PersistUI {
ViewVendor({this.vendorId, this.context});
final int vendorId;
final BuildContext context;
}
class EditVendor implements PersistUI {
EditVendor({this.vendor, this.context, this.completer, this.trackRoute = true});
final VendorEntity vendor;
final BuildContext context;
final Completer completer;
final bool trackRoute;
}
class UpdateVendor implements PersistUI {
UpdateVendor(this.vendor);
final VendorEntity vendor;
}
class LoadVendor {
LoadVendor({this.completer, this.vendorId, this.loadActivities = false});
final Completer completer;
final int vendorId;
final bool loadActivities;
}
class LoadVendorActivity {
LoadVendorActivity({this.completer, this.vendorId});
final Completer completer;
final int vendorId;
}
class LoadVendors {
LoadVendors({this.completer, this.force = false});
final Completer completer;
final bool force;
}
class LoadVendorRequest implements StartLoading {}
class LoadVendorFailure implements StopLoading {
LoadVendorFailure(this.error);
final dynamic error;
@override
String toString() {
return 'LoadVendorFailure{error: $error}';
}
}
class LoadVendorSuccess implements StopLoading, PersistData {
LoadVendorSuccess(this.vendor);
final VendorEntity vendor;
@override
String toString() {
return 'LoadVendorSuccess{vendor: $vendor}';
}
}
class LoadVendorsRequest implements StartLoading {}
class LoadVendorsFailure implements StopLoading {
LoadVendorsFailure(this.error);
final dynamic error;
@override
String toString() {
return 'LoadVendorsFailure{error: $error}';
}
}
class LoadVendorsSuccess implements StopLoading, PersistData {
LoadVendorsSuccess(this.vendors);
final BuiltList<VendorEntity> vendors;
@override
String toString() {
return 'LoadVendorsSuccess{vendors: $vendors}';
}
}
class SaveVendorRequest implements StartSaving {
SaveVendorRequest({this.completer, this.vendor});
final Completer completer;
final VendorEntity vendor;
}
class SaveVendorSuccess implements StopSaving, PersistData, PersistUI {
SaveVendorSuccess(this.vendor);
final VendorEntity vendor;
}
class AddVendorSuccess implements StopSaving, PersistData, PersistUI {
AddVendorSuccess(this.vendor);
final VendorEntity vendor;
}
class SaveVendorFailure implements StopSaving {
SaveVendorFailure (this.error);
final Object error;
}
class ArchiveVendorRequest implements StartSaving {
ArchiveVendorRequest(this.completer, this.vendorId);
final Completer completer;
final int vendorId;
}
class ArchiveVendorSuccess implements StopSaving, PersistData {
ArchiveVendorSuccess(this.vendor);
final VendorEntity vendor;
}
class ArchiveVendorFailure implements StopSaving {
ArchiveVendorFailure(this.vendor);
final VendorEntity vendor;
}
class DeleteVendorRequest implements StartSaving {
DeleteVendorRequest(this.completer, this.vendorId);
final Completer completer;
final int vendorId;
}
class DeleteVendorSuccess implements StopSaving, PersistData {
DeleteVendorSuccess(this.vendor);
final VendorEntity vendor;
}
class DeleteVendorFailure implements StopSaving {
DeleteVendorFailure(this.vendor);
final VendorEntity vendor;
}
class RestoreVendorRequest implements StartSaving {
RestoreVendorRequest(this.completer, this.vendorId);
final Completer completer;
final int vendorId;
}
class RestoreVendorSuccess implements StopSaving, PersistData {
RestoreVendorSuccess(this.vendor);
final VendorEntity vendor;
}
class RestoreVendorFailure implements StopSaving {
RestoreVendorFailure(this.vendor);
final VendorEntity vendor;
}
class FilterVendors {
FilterVendors(this.filter);
final String filter;
}
class SortVendors implements PersistUI {
SortVendors(this.field);
final String field;
}
class FilterVendorsByState implements PersistUI {
FilterVendorsByState(this.state);
final EntityState state;
}
class FilterVendorsByCustom1 implements PersistUI {
FilterVendorsByCustom1(this.value);
final String value;
}
class FilterVendorsByCustom2 implements PersistUI {
FilterVendorsByCustom2(this.value);
final String value;
}
class FilterVendorsByEntity implements PersistUI {
FilterVendorsByEntity({this.entityId, this.entityType});
final int entityId;
final EntityType entityType;
}

233
lib/redux/vendor/vendor_middleware.dart vendored Normal file
View File

@ -0,0 +1,233 @@
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/vendor/vendor_screen.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/redux/vendor/vendor_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/repositories/vendor_repository.dart';
List<Middleware<AppState>> createStoreVendorsMiddleware([
VendorRepository repository = const VendorRepository(),
]) {
final viewVendorList = _viewVendorList();
final viewVendor = _viewVendor();
final editVendor = _editVendor();
final loadVendors = _loadVendors(repository);
final loadVendor = _loadVendor(repository);
final saveVendor = _saveVendor(repository);
final archiveVendor = _archiveVendor(repository);
final deleteVendor = _deleteVendor(repository);
final restoreVendor = _restoreVendor(repository);
return [
TypedMiddleware<AppState, ViewVendorList>(viewVendorList),
TypedMiddleware<AppState, ViewVendor>(viewVendor),
TypedMiddleware<AppState, EditVendor>(editVendor),
TypedMiddleware<AppState, LoadVendors>(loadVendors),
TypedMiddleware<AppState, LoadVendor>(loadVendor),
TypedMiddleware<AppState, SaveVendorRequest>(saveVendor),
TypedMiddleware<AppState, ArchiveVendorRequest>(archiveVendor),
TypedMiddleware<AppState, DeleteVendorRequest>(deleteVendor),
TypedMiddleware<AppState, RestoreVendorRequest>(restoreVendor),
];
}
Middleware<AppState> _editVendor() {
return (Store<AppState> store, dynamic action, NextDispatcher next) async {
next(action);
store.dispatch(UpdateCurrentRoute(VendorEditScreen.route));
final vendor =
await Navigator.of(action.context).pushNamed(VendorEditScreen.route);
if (action.completer != null && vendor != null) {
action.completer.complete(vendor);
}
};
}
Middleware<AppState> _viewVendor() {
return (Store<AppState> store, dynamic action, NextDispatcher next) async {
next(action);
store.dispatch(UpdateCurrentRoute(VendorViewScreen.route));
Navigator.of(action.context).pushNamed(VendorViewScreen.route);
};
}
Middleware<AppState> _viewVendorList() {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
next(action);
store.dispatch(UpdateCurrentRoute(VendorScreen.route));
Navigator.of(action.context).pushNamedAndRemoveUntil(VendorScreen.route, (Route<dynamic> route) => false);
};
}
Middleware<AppState> _archiveVendor(VendorRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
final origVendor = store.state.vendorState.map[action.vendorId];
repository
.saveData(store.state.selectedCompany, store.state.authState,
origVendor, EntityAction.archive)
.then((VendorEntity vendor) {
store.dispatch(ArchiveVendorSuccess(vendor));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(ArchiveVendorFailure(origVendor));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _deleteVendor(VendorRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
final origVendor = store.state.vendorState.map[action.vendorId];
repository
.saveData(store.state.selectedCompany, store.state.authState,
origVendor, EntityAction.delete)
.then((VendorEntity vendor) {
store.dispatch(DeleteVendorSuccess(vendor));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(DeleteVendorFailure(origVendor));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _restoreVendor(VendorRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
final origVendor = store.state.vendorState.map[action.vendorId];
repository
.saveData(store.state.selectedCompany, store.state.authState,
origVendor, EntityAction.restore)
.then((VendorEntity vendor) {
store.dispatch(RestoreVendorSuccess(vendor));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(RestoreVendorFailure(origVendor));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _saveVendor(VendorRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
repository
.saveData(
store.state.selectedCompany, store.state.authState, action.vendor)
.then((VendorEntity vendor) {
if (action.vendor.isNew) {
store.dispatch(AddVendorSuccess(vendor));
} else {
store.dispatch(SaveVendorSuccess(vendor));
}
action.completer.complete(vendor);
}).catchError((Object error) {
print(error);
store.dispatch(SaveVendorFailure(error));
action.completer.completeError(error);
});
next(action);
};
}
Middleware<AppState> _loadVendor(VendorRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
final AppState state = store.state;
if (state.isLoading) {
next(action);
return;
}
store.dispatch(LoadVendorRequest());
repository
.loadItem(state.selectedCompany, state.authState, action.vendorId)
.then((vendor) {
store.dispatch(LoadVendorSuccess(vendor));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(LoadVendorFailure(error));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _loadVendors(VendorRepository repository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
final AppState state = store.state;
if (!state.vendorState.isStale && !action.force) {
next(action);
return;
}
if (state.isLoading) {
next(action);
return;
}
final int updatedAt = (state.vendorState.lastUpdated / 1000).round();
store.dispatch(LoadVendorsRequest());
repository
.loadList(state.selectedCompany, state.authState, updatedAt)
.then((data) {
store.dispatch(LoadVendorsSuccess(data));
if (action.completer != null) {
action.completer.complete(null);
}
/*
if (state.productState.isStale) {
store.dispatch(LoadProducts());
}
*/
}).catchError((Object error) {
print(error);
store.dispatch(LoadVendorsFailure(error));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}

197
lib/redux/vendor/vendor_reducer.dart vendored Normal file
View File

@ -0,0 +1,197 @@
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/vendor/vendor_actions.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart';
EntityUIState vendorUIReducer(VendorUIState state, dynamic action) {
return state.rebuild((b) => b
..listUIState.replace(vendorListReducer(state.listUIState, action))
..editing.replace(editingReducer(state.editing, action))
..selectedId = selectedIdReducer(state.selectedId, action));
}
Reducer<int> selectedIdReducer = combineReducers([
TypedReducer<int, ViewVendor>(
(int selectedId, dynamic action) => action.vendorId),
TypedReducer<int, AddVendorSuccess>(
(int selectedId, dynamic action) => action.vendor.id),
]);
final editingReducer = combineReducers<VendorEntity>([
TypedReducer<VendorEntity, SaveVendorSuccess>(_updateEditing),
TypedReducer<VendorEntity, AddVendorSuccess>(_updateEditing),
TypedReducer<VendorEntity, RestoreVendorSuccess>(_updateEditing),
TypedReducer<VendorEntity, ArchiveVendorSuccess>(_updateEditing),
TypedReducer<VendorEntity, DeleteVendorSuccess>(_updateEditing),
TypedReducer<VendorEntity, EditVendor>(_updateEditing),
TypedReducer<VendorEntity, UpdateVendor>(_updateEditing),
TypedReducer<VendorEntity, SelectCompany>(_clearEditing),
]);
VendorEntity _clearEditing(VendorEntity vendor, dynamic action) {
return VendorEntity();
}
VendorEntity _updateEditing(VendorEntity vendor, dynamic action) {
return action.vendor;
}
final vendorListReducer = combineReducers<ListUIState>([
TypedReducer<ListUIState, SortVendors>(_sortVendors),
TypedReducer<ListUIState, FilterVendorsByState>(_filterVendorsByState),
TypedReducer<ListUIState, FilterVendors>(_filterVendors),
TypedReducer<ListUIState, FilterVendorsByCustom1>(_filterVendorsByCustom1),
TypedReducer<ListUIState, FilterVendorsByCustom2>(_filterVendorsByCustom2),
TypedReducer<ListUIState, FilterVendorsByEntity>(_filterVendorsByClient),
]);
ListUIState _filterVendorsByClient(
ListUIState vendorListState, FilterVendorsByEntity action) {
return vendorListState.rebuild((b) => b
..filterEntityId = action.entityId
..filterEntityType = action.entityType);
}
ListUIState _filterVendorsByCustom1(
ListUIState vendorListState, FilterVendorsByCustom1 action) {
if (vendorListState.custom1Filters.contains(action.value)) {
return vendorListState
.rebuild((b) => b..custom1Filters.remove(action.value));
} else {
return vendorListState.rebuild((b) => b..custom1Filters.add(action.value));
}
}
ListUIState _filterVendorsByCustom2(
ListUIState vendorListState, FilterVendorsByCustom2 action) {
if (vendorListState.custom2Filters.contains(action.value)) {
return vendorListState
.rebuild((b) => b..custom2Filters.remove(action.value));
} else {
return vendorListState.rebuild((b) => b..custom2Filters.add(action.value));
}
}
ListUIState _filterVendorsByState(
ListUIState vendorListState, FilterVendorsByState action) {
if (vendorListState.stateFilters.contains(action.state)) {
return vendorListState.rebuild((b) => b..stateFilters.remove(action.state));
} else {
return vendorListState.rebuild((b) => b..stateFilters.add(action.state));
}
}
ListUIState _filterVendors(ListUIState vendorListState, FilterVendors action) {
return vendorListState.rebuild((b) => b..filter = action.filter);
}
ListUIState _sortVendors(ListUIState vendorListState, SortVendors action) {
return vendorListState.rebuild((b) => b
..sortAscending = b.sortField != action.field || !b.sortAscending
..sortField = action.field);
}
final vendorsReducer = combineReducers<VendorState>([
TypedReducer<VendorState, SaveVendorSuccess>(_updateVendor),
TypedReducer<VendorState, AddVendorSuccess>(_addVendor),
TypedReducer<VendorState, LoadVendorsSuccess>(_setLoadedVendors),
TypedReducer<VendorState, LoadVendorSuccess>(_setLoadedVendor),
TypedReducer<VendorState, ArchiveVendorRequest>(_archiveVendorRequest),
TypedReducer<VendorState, ArchiveVendorSuccess>(_archiveVendorSuccess),
TypedReducer<VendorState, ArchiveVendorFailure>(_archiveVendorFailure),
TypedReducer<VendorState, DeleteVendorRequest>(_deleteVendorRequest),
TypedReducer<VendorState, DeleteVendorSuccess>(_deleteVendorSuccess),
TypedReducer<VendorState, DeleteVendorFailure>(_deleteVendorFailure),
TypedReducer<VendorState, RestoreVendorRequest>(_restoreVendorRequest),
TypedReducer<VendorState, RestoreVendorSuccess>(_restoreVendorSuccess),
TypedReducer<VendorState, RestoreVendorFailure>(_restoreVendorFailure),
]);
VendorState _archiveVendorRequest(
VendorState vendorState, ArchiveVendorRequest action) {
final vendor = vendorState.map[action.vendorId]
.rebuild((b) => b..archivedAt = DateTime.now().millisecondsSinceEpoch);
return vendorState.rebuild((b) => b..map[action.vendorId] = vendor);
}
VendorState _archiveVendorSuccess(
VendorState vendorState, ArchiveVendorSuccess action) {
return vendorState.rebuild((b) => b..map[action.vendor.id] = action.vendor);
}
VendorState _archiveVendorFailure(
VendorState vendorState, ArchiveVendorFailure action) {
return vendorState.rebuild((b) => b..map[action.vendor.id] = action.vendor);
}
VendorState _deleteVendorRequest(
VendorState vendorState, DeleteVendorRequest action) {
final vendor = vendorState.map[action.vendorId].rebuild((b) => b
..archivedAt = DateTime.now().millisecondsSinceEpoch
..isDeleted = true);
return vendorState.rebuild((b) => b..map[action.vendorId] = vendor);
}
VendorState _deleteVendorSuccess(
VendorState vendorState, DeleteVendorSuccess action) {
return vendorState.rebuild((b) => b..map[action.vendor.id] = action.vendor);
}
VendorState _deleteVendorFailure(
VendorState vendorState, DeleteVendorFailure action) {
return vendorState.rebuild((b) => b..map[action.vendor.id] = action.vendor);
}
VendorState _restoreVendorRequest(
VendorState vendorState, RestoreVendorRequest action) {
final vendor = vendorState.map[action.vendorId].rebuild((b) => b
..archivedAt = null
..isDeleted = false);
return vendorState.rebuild((b) => b..map[action.vendorId] = vendor);
}
VendorState _restoreVendorSuccess(
VendorState vendorState, RestoreVendorSuccess action) {
return vendorState.rebuild((b) => b..map[action.vendor.id] = action.vendor);
}
VendorState _restoreVendorFailure(
VendorState vendorState, RestoreVendorFailure action) {
return vendorState.rebuild((b) => b..map[action.vendor.id] = action.vendor);
}
VendorState _addVendor(VendorState vendorState, AddVendorSuccess action) {
return vendorState.rebuild((b) => b
..map[action.vendor.id] = action.vendor
..list.add(action.vendor.id));
}
VendorState _updateVendor(VendorState vendorState, SaveVendorSuccess action) {
return vendorState.rebuild((b) => b
..map[action.vendor.id] = action.vendor);
}
VendorState _setLoadedVendor(
VendorState vendorState, LoadVendorSuccess action) {
return vendorState.rebuild((b) => b
..map[action.vendor.id] = action.vendor);
}
VendorState _setLoadedVendors(
VendorState vendorState, LoadVendorsSuccess action) {
final state = vendorState.rebuild((b) => b
..lastUpdated = DateTime.now().millisecondsSinceEpoch
..map.addAll(Map.fromIterable(
action.vendors,
key: (dynamic item) => item.id,
value: (dynamic item) => item,
)));
return state.rebuild((b) => b..list.replace(state.map.keys));
}

74
lib/redux/vendor/vendor_selectors.dart vendored Normal file
View File

@ -0,0 +1,74 @@
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 memoizedDropdownVendorList = memo3(
(BuiltMap<int, VendorEntity> vendorMap, BuiltList<int> vendorList,
int clientId) =>
dropdownVendorsSelector(vendorMap, vendorList, clientId));
List<int> dropdownVendorsSelector(BuiltMap<int, VendorEntity> vendorMap,
BuiltList<int> vendorList, int clientId) {
final list = vendorList.where((vendorId) {
final vendor = vendorMap[vendorId];
/*
if (clientId != null && clientId > 0 && vendor.clientId != clientId) {
return false;
}
*/
return vendor.isActive;
}).toList();
list.sort((vendorAId, vendorBId) {
final vendorA = vendorMap[vendorAId];
final vendorB = vendorMap[vendorBId];
return vendorA.compareTo(vendorB, VendorFields.name, true);
});
return list;
}
var memoizedFilteredVendorList = memo3((BuiltMap<int, VendorEntity> vendorMap,
BuiltList<int> vendorList, ListUIState vendorListState) =>
filteredVendorsSelector(vendorMap, vendorList, vendorListState));
List<int> filteredVendorsSelector(BuiltMap<int, VendorEntity> vendorMap,
BuiltList<int> vendorList, ListUIState vendorListState) {
final list = vendorList.where((vendorId) {
final vendor = vendorMap[vendorId];
if (!vendor.matchesStates(vendorListState.stateFilters)) {
return false;
}
/*
if (vendorListState.filterEntityId != null &&
vendor.clientId != vendorListState.filterEntityId) {
return false;
}
*/
if (vendorListState.custom1Filters.isNotEmpty &&
!vendorListState.custom1Filters.contains(vendor.customValue1)) {
return false;
}
if (vendorListState.custom2Filters.isNotEmpty &&
!vendorListState.custom2Filters.contains(vendor.customValue2)) {
return false;
}
/*
if (vendorListState.filterEntityId != null &&
vendor.entityId != vendorListState.filterEntityId) {
return false;
}
*/
return vendor.matchesFilter(vendorListState.filter);
}).toList();
list.sort((vendorAId, vendorBId) {
final vendorA = vendorMap[vendorAId];
final vendorB = vendorMap[vendorBId];
return vendorA.compareTo(
vendorB, vendorListState.sortField, vendorListState.sortAscending);
});
return list;
}

59
lib/redux/vendor/vendor_state.dart vendored Normal file
View File

@ -0,0 +1,59 @@
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/vendor_model.dart';
import 'package:invoiceninja_flutter/redux/ui/entity_ui_state.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
part 'vendor_state.g.dart';
abstract class VendorState implements Built<VendorState, VendorStateBuilder> {
factory VendorState() {
return _$VendorState._(
lastUpdated: 0,
map: BuiltMap<int, VendorEntity>(),
list: BuiltList<int>(),
);
}
VendorState._();
@nullable
int get lastUpdated;
BuiltMap<int, VendorEntity> 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<VendorState> get serializer => _$vendorStateSerializer;
}
abstract class VendorUIState extends Object with EntityUIState implements Built<VendorUIState, VendorUIStateBuilder> {
factory VendorUIState() {
return _$VendorUIState._(
listUIState: ListUIState(VendorFields.name),
editing: VendorEntity(),
selectedId: 0,
);
}
VendorUIState._();
@nullable
VendorEntity get editing;
@override
bool get isCreatingNew => editing.isNew;
static Serializer<VendorUIState> get serializer => _$vendorUIStateSerializer;
}

388
lib/redux/vendor/vendor_state.g.dart vendored Normal file
View File

@ -0,0 +1,388 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'vendor_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<VendorState> _$vendorStateSerializer = new _$VendorStateSerializer();
Serializer<VendorUIState> _$vendorUIStateSerializer =
new _$VendorUIStateSerializer();
class _$VendorStateSerializer implements StructuredSerializer<VendorState> {
@override
final Iterable<Type> types = const [VendorState, _$VendorState];
@override
final String wireName = 'VendorState';
@override
Iterable serialize(Serializers serializers, VendorState object,
{FullType specifiedType = FullType.unspecified}) {
final result = <Object>[
'map',
serializers.serialize(object.map,
specifiedType: const FullType(BuiltMap,
const [const FullType(int), const FullType(VendorEntity)])),
'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
VendorState deserialize(Serializers serializers, Iterable serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new VendorStateBuilder();
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(VendorEntity)
])) 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 _$VendorUIStateSerializer implements StructuredSerializer<VendorUIState> {
@override
final Iterable<Type> types = const [VendorUIState, _$VendorUIState];
@override
final String wireName = 'VendorUIState';
@override
Iterable serialize(Serializers serializers, VendorUIState 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(VendorEntity)));
}
return result;
}
@override
VendorUIState deserialize(Serializers serializers, Iterable serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new VendorUIStateBuilder();
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(VendorEntity)) as VendorEntity);
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 _$VendorState extends VendorState {
@override
final int lastUpdated;
@override
final BuiltMap<int, VendorEntity> map;
@override
final BuiltList<int> list;
factory _$VendorState([void updates(VendorStateBuilder b)]) =>
(new VendorStateBuilder()..update(updates)).build();
_$VendorState._({this.lastUpdated, this.map, this.list}) : super._() {
if (map == null) {
throw new BuiltValueNullFieldError('VendorState', 'map');
}
if (list == null) {
throw new BuiltValueNullFieldError('VendorState', 'list');
}
}
@override
VendorState rebuild(void updates(VendorStateBuilder b)) =>
(toBuilder()..update(updates)).build();
@override
VendorStateBuilder toBuilder() => new VendorStateBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is VendorState &&
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('VendorState')
..add('lastUpdated', lastUpdated)
..add('map', map)
..add('list', list))
.toString();
}
}
class VendorStateBuilder implements Builder<VendorState, VendorStateBuilder> {
_$VendorState _$v;
int _lastUpdated;
int get lastUpdated => _$this._lastUpdated;
set lastUpdated(int lastUpdated) => _$this._lastUpdated = lastUpdated;
MapBuilder<int, VendorEntity> _map;
MapBuilder<int, VendorEntity> get map =>
_$this._map ??= new MapBuilder<int, VendorEntity>();
set map(MapBuilder<int, VendorEntity> map) => _$this._map = map;
ListBuilder<int> _list;
ListBuilder<int> get list => _$this._list ??= new ListBuilder<int>();
set list(ListBuilder<int> list) => _$this._list = list;
VendorStateBuilder();
VendorStateBuilder get _$this {
if (_$v != null) {
_lastUpdated = _$v.lastUpdated;
_map = _$v.map?.toBuilder();
_list = _$v.list?.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(VendorState other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$VendorState;
}
@override
void update(void updates(VendorStateBuilder b)) {
if (updates != null) updates(this);
}
@override
_$VendorState build() {
_$VendorState _$result;
try {
_$result = _$v ??
new _$VendorState._(
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(
'VendorState', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
}
class _$VendorUIState extends VendorUIState {
@override
final VendorEntity editing;
@override
final int selectedId;
@override
final ListUIState listUIState;
factory _$VendorUIState([void updates(VendorUIStateBuilder b)]) =>
(new VendorUIStateBuilder()..update(updates)).build();
_$VendorUIState._({this.editing, this.selectedId, this.listUIState})
: super._() {
if (selectedId == null) {
throw new BuiltValueNullFieldError('VendorUIState', 'selectedId');
}
if (listUIState == null) {
throw new BuiltValueNullFieldError('VendorUIState', 'listUIState');
}
}
@override
VendorUIState rebuild(void updates(VendorUIStateBuilder b)) =>
(toBuilder()..update(updates)).build();
@override
VendorUIStateBuilder toBuilder() => new VendorUIStateBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is VendorUIState &&
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('VendorUIState')
..add('editing', editing)
..add('selectedId', selectedId)
..add('listUIState', listUIState))
.toString();
}
}
class VendorUIStateBuilder
implements Builder<VendorUIState, VendorUIStateBuilder> {
_$VendorUIState _$v;
VendorEntityBuilder _editing;
VendorEntityBuilder get editing =>
_$this._editing ??= new VendorEntityBuilder();
set editing(VendorEntityBuilder 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;
VendorUIStateBuilder();
VendorUIStateBuilder get _$this {
if (_$v != null) {
_editing = _$v.editing?.toBuilder();
_selectedId = _$v.selectedId;
_listUIState = _$v.listUIState?.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(VendorUIState other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$VendorUIState;
}
@override
void update(void updates(VendorUIStateBuilder b)) {
if (updates != null) updates(this);
}
@override
_$VendorUIState build() {
_$VendorUIState _$result;
try {
_$result = _$v ??
new _$VendorUIState._(
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(
'VendorUIState', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
}

View File

@ -19,6 +19,8 @@ import 'package:redux/redux.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart';
import 'package:invoiceninja_flutter/redux/task/task_actions.dart'; import 'package:invoiceninja_flutter/redux/task/task_actions.dart';
import 'package:invoiceninja_flutter/redux/project/project_actions.dart'; import 'package:invoiceninja_flutter/redux/project/project_actions.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_actions.dart'; import 'package:invoiceninja_flutter/redux/payment/payment_actions.dart';
@ -213,7 +215,6 @@ class AppDrawer extends StatelessWidget {
quote: InvoiceEntity(isQuote: true), context: context)); quote: InvoiceEntity(isQuote: true), context: context));
}, },
), ),
// STARTER: menu - do not remove comment
DrawerTile( DrawerTile(
company: company, company: company,
entityType: EntityType.project, entityType: EntityType.project,
@ -239,45 +240,19 @@ class AppDrawer extends StatelessWidget {
context: context)); context: context));
}, },
), ),
ListTile( DrawerTile(
dense: true, company: company,
leading: Icon(FontAwesomeIcons.building, size: 22.0), entityType: EntityType.vendor,
title: Text('Vendors & Expenses'), icon: getEntityIcon(EntityType.vendor),
onTap: () { title: localization.vendors,
showDialog<AlertDialog>( onTap: () => store.dispatch(ViewVendorList(context)),
context: context, onCreateTap: () {
builder: (BuildContext context) => AlertDialog( navigator.pop();
semanticLabel: 'Vendors & Expenses', store.dispatch(
title: Text('Vendors & Expenses'), EditVendor(vendor: VendorEntity(), context: context));
content: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
style: aboutTextStyle,
text: localization.thanksForPatience + ' ',
),
_LinkTextSpan(
style: linkStyle,
url: getLegacyAppURL(context),
text: localization.legacyMobileApp,
),
TextSpan(
style: aboutTextStyle,
text: '.',
),
],
),
),
actions: <Widget>[
FlatButton(
child: Text(localization.ok.toUpperCase()),
onPressed: () => Navigator.pop(context),
)
],
),
);
}, },
), ),
// STARTER: menu - do not remove comment
DrawerTile( DrawerTile(
company: company, company: company,
icon: FontAwesomeIcons.cog, icon: FontAwesomeIcons.cog,

208
lib/ui/vendor/edit/vendor_edit.dart vendored Normal file
View File

@ -0,0 +1,208 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/vendor/edit/vendor_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/app/buttons/action_icon_button.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class VendorEdit extends StatefulWidget {
const VendorEdit({
Key key,
@required this.viewModel,
}) : super(key: key);
final VendorEditVM viewModel;
@override
_VendorEditState createState() => _VendorEditState();
}
class _VendorEditState extends State<VendorEdit> {
static final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
// STARTER: controllers - do not remove comment
final _nameController = TextEditingController();
final _address1Controller = TextEditingController();
final _address2Controller = TextEditingController();
final _cityController = TextEditingController();
final _stateController = TextEditingController();
final _postalCodeController = TextEditingController();
List<TextEditingController> _controllers = [];
@override
void didChangeDependencies() {
_controllers = [
// STARTER: array - do not remove comment
_nameController,
_address1Controller,
_address2Controller,
_cityController,
_stateController,
_postalCodeController,
];
_controllers.forEach((controller) => controller.removeListener(_onChanged));
final vendor = widget.viewModel.vendor;
// STARTER: read value - do not remove comment
_nameController.text = vendor.name;
_address1Controller.text = vendor.address1;
_address2Controller.text = vendor.address2;
_cityController.text = vendor.city;
_stateController.text = vendor.state;
_postalCodeController.text = vendor.postalCode;
_controllers.forEach((controller) => controller.addListener(_onChanged));
super.didChangeDependencies();
}
@override
void dispose() {
_controllers.forEach((controller) {
controller.removeListener(_onChanged);
controller.dispose();
});
super.dispose();
}
void _onChanged() {
final vendor = widget.viewModel.vendor.rebuild((b) => b
// STARTER: set value - do not remove comment
..name = _nameController.text.trim()
..address1 = _address1Controller.text.trim()
..address2 = _address2Controller.text.trim()
..city = _cityController.text.trim()
..state = _stateController.text.trim()
..postalCode = _postalCodeController.text.trim()
);
if (vendor != widget.viewModel.vendor) {
widget.viewModel.onChanged(vendor);
}
}
@override
Widget build(BuildContext context) {
final viewModel = widget.viewModel;
final localization = AppLocalization.of(context);
final vendor = viewModel.vendor;
return WillPopScope(
onWillPop: () async {
viewModel.onBackPressed();
return true;
},
child: Scaffold(
appBar: AppBar(
title: Text(viewModel.vendor.isNew
? localization.newVendor
: localization.editVendor),
actions: <Widget>[
ActionIconButton(
icon: Icons.cloud_upload,
tooltip: localization.save,
isVisible: !vendor.isDeleted,
isDirty: vendor.isNew || vendor != viewModel.origVendor,
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
TextFormField(
controller: _nameController,
autocorrect: false,
decoration: InputDecoration(
labelText: 'Name',
),
),
TextFormField(
controller: _address1Controller,
autocorrect: false,
decoration: InputDecoration(
labelText: 'Address1',
),
),
TextFormField(
controller: _address2Controller,
autocorrect: false,
decoration: InputDecoration(
labelText: 'Address2',
),
),
TextFormField(
controller: _cityController,
autocorrect: false,
decoration: InputDecoration(
labelText: 'City',
),
),
TextFormField(
controller: _stateController,
autocorrect: false,
decoration: InputDecoration(
labelText: 'State',
),
),
TextFormField(
controller: _postalCodeController,
autocorrect: false,
decoration: InputDecoration(
labelText: 'PostalCode',
),
),
],
),
],
);
})
),
),
);
}
}

99
lib/ui/vendor/edit/vendor_edit_vm.dart vendored Normal file
View File

@ -0,0 +1,99 @@
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/vendor/vendor_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/vendor/view/vendor_view_vm.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart';
import 'package:invoiceninja_flutter/data/models/vendor_model.dart';
import 'package:invoiceninja_flutter/ui/vendor/edit/vendor_edit.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class VendorEditScreen extends StatelessWidget {
const VendorEditScreen({Key key}) : super(key: key);
static const String route = '/vendor/edit';
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, VendorEditVM>(
converter: (Store<AppState> store) {
return VendorEditVM.fromStore(store);
},
builder: (context, viewModel) {
return VendorEdit(
viewModel: viewModel,
);
},
);
}
}
class VendorEditVM {
VendorEditVM({
@required this.state,
@required this.vendor,
@required this.company,
@required this.onChanged,
@required this.isSaving,
@required this.origVendor,
@required this.onSavePressed,
@required this.onBackPressed,
@required this.isLoading,
});
factory VendorEditVM.fromStore(Store<AppState> store) {
final vendor = store.state.vendorUIState.editing;
final state = store.state;
return VendorEditVM(
state: state,
isLoading: state.isLoading,
isSaving: state.isSaving,
origVendor: state.vendorState.map[vendor.id],
vendor: vendor,
company: state.selectedCompany,
onChanged: (VendorEntity vendor) {
store.dispatch(UpdateVendor(vendor));
},
onBackPressed: () {
if (state.uiState.currentRoute.contains(VendorScreen.route)) {
store.dispatch(UpdateCurrentRoute(vendor.isNew ? VendorScreen.route : VendorViewScreen.route));
}
},
onSavePressed: (BuildContext context) {
final Completer<VendorEntity> completer = new Completer<VendorEntity>();
store.dispatch(SaveVendorRequest(completer: completer, vendor: vendor));
return completer.future.then((_) {
return completer.future.then((savedVendor) {
store.dispatch(UpdateCurrentRoute(VendorViewScreen.route));
if (vendor.isNew) {
Navigator.of(context).pushReplacementNamed(VendorViewScreen.route);
} else {
Navigator.of(context).pop(savedVendor);
}
}).catchError((Object error) {
showDialog<ErrorDialog>(
context: context,
builder: (BuildContext context) {
return ErrorDialog(error);
});
});
});
},
);
}
final VendorEntity vendor;
final CompanyEntity company;
final Function(VendorEntity) onChanged;
final Function(BuildContext) onSavePressed;
final Function onBackPressed;
final bool isLoading;
final bool isSaving;
final VendorEntity origVendor;
final AppState state;
}

212
lib/ui/vendor/vendor_list.dart vendored Normal file
View File

@ -0,0 +1,212 @@
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/vendor/vendor_list_item.dart';
import 'package:invoiceninja_flutter/ui/vendor/vendor_list_vm.dart';
import 'package:invoiceninja_flutter/utils/icons.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class VendorList extends StatelessWidget {
const VendorList({
Key key,
@required this.viewModel,
}) : super(key: key);
final VendorListVM viewModel;
void _showMenu(
BuildContext context, VendorEntity vendor, ClientEntity client) async {
if (vendor == null || client == null) {
return;
}
final user = viewModel.user;
final message = await showDialog<String>(
context: context,
builder: (BuildContext dialogContext) => SimpleDialog(
children: vendor
.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, vendor, 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.vendorList.isEmpty
? Opacity(
opacity: 0.5,
child: Center(
child: Text(
AppLocalization.of(context).noRecordsFound,
style: TextStyle(
fontSize: 18.0,
),
),
),
)
: ListView.builder(
shrinkWrap: true,
itemCount: viewModel.vendorList.length,
itemBuilder: (BuildContext context, index) {
final vendorId = viewModel.vendorList[index];
final vendor = viewModel.vendorMap[vendorId];
return Column(
children: <Widget>[
VendorListItem(
user: viewModel.user,
filter: viewModel.filter,
vendor: vendor,
onTap: () =>
viewModel.onVendorTap(context, vendor),
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
_showMenu(context, vendor, null);
} else {
viewModel.onEntityAction(
context, vendor, action);
}
},
onLongPress: () =>
_showMenu(context, vendor, 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.vendorList.isEmpty
? Opacity(
opacity: 0.5,
child: Center(
child: Text(
AppLocalization.of(context).noRecordsFound,
style: TextStyle(
fontSize: 18.0,
),
),
),
)
: ListView.builder(
shrinkWrap: true,
itemCount: viewModel.vendorList.length,
itemBuilder: (BuildContext context, index) {
final vendorId = viewModel.vendorList[index];
final vendor = viewModel.vendorMap[vendorId];
final client =
viewModel.clientMap[vendor.clientId] ??
ClientEntity();
return Column(
children: <Widget>[
VendorListItem(
user: viewModel.user,
filter: viewModel.filter,
vendor: vendor,
client:
viewModel.clientMap[vendor.clientId] ??
ClientEntity(),
onTap: () =>
viewModel.onVendorTap(context, vendor),
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
_showMenu(context, vendor, client);
} else {
viewModel.onEntityAction(
context, vendor, action);
}
},
onLongPress: () =>
_showMenu(context, vendor, client),
),
Divider(
height: 1.0,
),
],
);
},
),
),
),*/
],
);
}
}

85
lib/ui/vendor/vendor_list_item.dart vendored Normal file
View File

@ -0,0 +1,85 @@
import 'package:invoiceninja_flutter/ui/app/entity_state_label.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/dismissible_entity.dart';
class VendorListItem extends StatelessWidget {
const VendorListItem({
@required this.user,
@required this.onEntityAction,
@required this.onTap,
@required this.onLongPress,
//@required this.onCheckboxChanged,
@required this.vendor,
@required this.filter,
});
final UserEntity user;
final Function(EntityAction) onEntityAction;
final GestureTapCallback onTap;
final GestureTapCallback onLongPress;
//final ValueChanged<bool> onCheckboxChanged;
final VendorEntity vendor;
final String filter;
static final vendorItemKey = (int id) => Key('__vendor_item_${id}__');
@override
Widget build(BuildContext context) {
final filterMatch = filter != null && filter.isNotEmpty
? vendor.matchesFilterValue(filter)
: null;
final subtitle = filterMatch;
return DismissibleEntity(
user: user,
entity: vendor,
onEntityAction: onEntityAction,
child: ListTile(
onTap: onTap,
onLongPress: onLongPress,
/*
leading: Checkbox(
//key: NinjaKeys.vendorItemCheckbox(vendor.id),
value: true,
//onChanged: onCheckboxChanged,
onChanged: (value) {
return true;
},
),
*/
title: Container(
width: MediaQuery.of(context).size.width,
child: Row(
children: <Widget>[
Expanded(
child: Text(
vendor.name,
//key: NinjaKeys.clientItemClientKey(client.id),
style: Theme.of(context).textTheme.title,
),
),
Text(formatNumber(vendor.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(vendor),
],
),
),
);
}
}

129
lib/ui/vendor/vendor_list_vm.dart vendored Normal file
View File

@ -0,0 +1,129 @@
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/vendor/vendor_selectors.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/vendor/vendor_list.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart';
class VendorListBuilder extends StatelessWidget {
const VendorListBuilder({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, VendorListVM>(
converter: VendorListVM.fromStore,
builder: (context, viewModel) {
return VendorList(
viewModel: viewModel,
);
},
);
}
}
class VendorListVM {
VendorListVM({
@required this.user,
@required this.vendorList,
@required this.vendorMap,
@required this.filter,
@required this.isLoading,
@required this.isLoaded,
@required this.onVendorTap,
@required this.listState,
@required this.onRefreshed,
@required this.onEntityAction,
@required this.onClearEntityFilterPressed,
@required this.onViewEntityFilterPressed,
});
static VendorListVM 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(LoadVendors(completer: completer, force: true));
return completer.future;
}
final state = store.state;
return VendorListVM(
user: state.user,
listState: state.vendorListState,
vendorList: memoizedFilteredVendorList(state.vendorState.map,
state.vendorState.list, state.vendorListState),
vendorMap: state.vendorState.map,
isLoading: state.isLoading,
isLoaded: state.vendorState.isLoaded,
filter: state.vendorUIState.listUIState.filter,
onClearEntityFilterPressed: () =>
store.dispatch(FilterVendorsByEntity()),
onViewEntityFilterPressed: (BuildContext context) => store.dispatch(
ViewClient(
clientId: state.vendorListState.filterEntityId,
context: context)),
onVendorTap: (context, vendor) {
store.dispatch(ViewVendor(vendorId: vendor.id, context: context));
},
onEntityAction: (context, vendor, action) {
switch (action) {
case EntityAction.edit:
store.dispatch(
EditVendor(context: context, vendor: vendor));
break;
case EntityAction.clone:
Navigator.of(context).pop();
store.dispatch(
EditVendor(context: context, vendor: vendor.clone));
break;
case EntityAction.restore:
store.dispatch(RestoreVendorRequest(
snackBarCompleter(
context, AppLocalization.of(context).restoredVendor),
vendor.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveVendorRequest(
snackBarCompleter(
context, AppLocalization.of(context).archivedVendor),
vendor.id));
break;
case EntityAction.delete:
store.dispatch(DeleteVendorRequest(
snackBarCompleter(
context, AppLocalization.of(context).deletedVendor),
vendor.id));
break;
}
},
onRefreshed: (context) => _handleRefresh(context),
);
}
final UserEntity user;
final List<int> vendorList;
final BuiltMap<int, VendorEntity> vendorMap;
final ListUIState listState;
final String filter;
final bool isLoading;
final bool isLoaded;
final Function(BuildContext, VendorEntity) onVendorTap;
final Function(BuildContext) onRefreshed;
final Function(BuildContext, VendorEntity, EntityAction) onEntityAction;
final Function onClearEntityFilterPressed;
final Function(BuildContext) onViewEntityFilterPressed;
}

85
lib/ui/vendor/vendor_screen.dart vendored Normal file
View File

@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/dashboard/dashboard_actions.dart';
import 'package:invoiceninja_flutter/ui/app/list_filter.dart';
import 'package:invoiceninja_flutter/ui/app/list_filter_button.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/vendor/vendor_list_vm.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart';
import 'package:invoiceninja_flutter/ui/app/app_drawer_vm.dart';
import 'package:invoiceninja_flutter/ui/app/app_bottom_bar.dart';
class VendorScreen extends StatelessWidget {
static const String route = '/vendor';
@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.vendor,
onFilterChanged: (value) {
store.dispatch(FilterVendors(value));
},
),
actions: [
ListFilterButton(
entityType: EntityType.vendor,
onFilterPressed: (String value) {
store.dispatch(FilterVendors(value));
},
),
],
),
drawer: AppDrawerBuilder(),
body: VendorListBuilder(),
bottomNavigationBar: AppBottomBar(
entityType: EntityType.vendor,
onSelectedSortField: (value) => store.dispatch(SortVendors(value)),
customValues1: company.getCustomFieldValues(CustomFieldType.vendor1,
excludeBlank: true),
customValues2: company.getCustomFieldValues(CustomFieldType.vendor2,
excludeBlank: true),
onSelectedCustom1: (value) =>
store.dispatch(FilterVendorsByCustom1(value)),
onSelectedCustom2: (value) =>
store.dispatch(FilterVendorsByCustom2(value)),
sortFields: [
VendorFields.updatedAt,
],
onSelectedState: (EntityState state, value) {
store.dispatch(FilterVendorsByState(state));
},
),
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
floatingActionButton: user.canCreate(EntityType.vendor)
? FloatingActionButton(
//key: Key(VendorKeys.vendorScreenFABKeyString),
backgroundColor: Theme.of(context).primaryColorDark,
onPressed: () {
store.dispatch(
EditVendor(vendor: VendorEntity(), context: context));
},
child: Icon(
Icons.add,
color: Colors.white,
),
tooltip: localization.newVendor,
)
: null,
),
);
}
}

53
lib/ui/vendor/view/vendor_view.dart vendored Normal file
View File

@ -0,0 +1,53 @@
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/vendor/view/vendor_view_vm.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
class VendorView extends StatefulWidget {
const VendorView({
Key key,
@required this.viewModel,
}) : super(key: key);
final VendorViewVM viewModel;
@override
_VendorViewState createState() => new _VendorViewState();
}
class _VendorViewState extends State<VendorView> {
@override
Widget build(BuildContext context) {
final viewModel = widget.viewModel;
final vendor = viewModel.vendor;
return Scaffold(
appBar: AppBar(
title: Text(vendor.name),
actions: vendor.isNew
? []
: [
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
viewModel.onEditPressed(context);
},
),
ActionMenuButton(
user: viewModel.company.user,
isSaving: viewModel.isSaving,
entity: vendor,
onSelected: viewModel.onActionSelected,
),
],
),
body: FormCard(
children: [
// STARTER: widgets - do not remove comment
]
),
);
}
}

108
lib/ui/vendor/view/vendor_view_vm.dart vendored Normal file
View File

@ -0,0 +1,108 @@
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/vendor/vendor_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/vendor/vendor_actions.dart';
import 'package:invoiceninja_flutter/data/models/vendor_model.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/vendor/view/vendor_view.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class VendorViewScreen extends StatelessWidget {
const VendorViewScreen({Key key}) : super(key: key);
static const String route = '/vendor/view';
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, VendorViewVM>(
converter: (Store<AppState> store) {
return VendorViewVM.fromStore(store);
},
builder: (context, vm) {
return VendorView(
viewModel: vm,
);
},
);
}
}
class VendorViewVM {
VendorViewVM({
@required this.state,
@required this.vendor,
@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 VendorViewVM.fromStore(Store<AppState> store) {
final state = store.state;
final vendor = state.vendorState.map[state.vendorUIState.selectedId];
Future<Null> _handleRefresh(BuildContext context) {
final completer = snackBarCompleter(
context, AppLocalization.of(context).refreshComplete);
store.dispatch(LoadVendor(completer: completer, vendorId: vendor.id));
return completer.future;
}
return VendorViewVM(
state: state,
company: state.selectedCompany,
isSaving: state.isSaving,
isLoading: state.isLoading,
isDirty: vendor.isNew,
vendor: vendor,
onEditPressed: (BuildContext context) {
store.dispatch(EditVendor(vendor: vendor, context: context));
},
onRefreshed: (context) => _handleRefresh(context),
onBackPressed: () {
if (state.uiState.currentRoute.contains(VendorScreen.route)) {
store.dispatch(UpdateCurrentRoute(VendorScreen.route));
}
},
onActionSelected: (BuildContext context, EntityAction action) {
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.archive:
store.dispatch(ArchiveVendorRequest(
popCompleter(context, localization.archivedVendor),
vendor.id));
break;
case EntityAction.delete:
store.dispatch(DeleteVendorRequest(
popCompleter(context, localization.deletedVendor),
vendor.id));
break;
case EntityAction.restore:
store.dispatch(RestoreVendorRequest(
snackBarCompleter(context, localization.restoredVendor),
vendor.id));
break;
}
});
}
final AppState state;
final VendorEntity vendor;
final CompanyEntity company;
final Function(BuildContext, EntityAction) onActionSelected;
final Function(BuildContext) onEditPressed;
final Function onBackPressed;
final Function(BuildContext) onRefreshed;
final bool isSaving;
final bool isLoading;
final bool isDirty;
}

View File

@ -88,9 +88,6 @@ class AppLocalization {
'hosted_login': 'Hosted Login', 'hosted_login': 'Hosted Login',
'selfhost_login': 'Selfhost Login', 'selfhost_login': 'Selfhost Login',
'google_login': 'Google Login', 'google_login': 'Google Login',
'thanks_for_patience':
'Thank for your patience while we work to implement these features.\n\nWe hope to have them completed in the next few months.\n\nUntil then we\'ll continue to support the',
'legacy_mobile_app': 'legacy mobile app',
'today': 'Today', 'today': 'Today',
'custom_range': 'Custom', 'custom_range': 'Custom',
'date_range': 'Date Range', 'date_range': 'Date Range',
@ -11465,12 +11462,6 @@ class AppLocalization {
String get googleLogin => _localizedValues[locale.toString()]['google_login']; String get googleLogin => _localizedValues[locale.toString()]['google_login'];
String get thanksForPatience =>
_localizedValues[locale.toString()]['thanks_for_patience'];
String get legacyMobileApp =>
_localizedValues[locale.toString()]['legacy_mobile_app'];
String get today => _localizedValues[locale.toString()]['today']; String get today => _localizedValues[locale.toString()]['today'];
String get customRange => _localizedValues[locale.toString()]['custom_range']; String get customRange => _localizedValues[locale.toString()]['custom_range'];