Projects
This commit is contained in:
parent
53ed6494d4
commit
ee470f6eff
|
|
@ -22,6 +22,9 @@ import 'package:invoiceninja_flutter/redux/client/client_state.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/ui/ui_state.dart';
|
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/data/models/project_model.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';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
||||||
|
|
@ -75,6 +78,8 @@ part 'serializers.g.dart';
|
||||||
TimezoneItemResponse,
|
TimezoneItemResponse,
|
||||||
TimezoneListResponse,
|
TimezoneListResponse,
|
||||||
// STARTER: serializers - do not remove comment
|
// STARTER: serializers - do not remove comment
|
||||||
|
ProjectEntity,
|
||||||
|
|
||||||
PaymentEntity,
|
PaymentEntity,
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,8 @@ Serializers _$serializers = (new Serializers().toBuilder()
|
||||||
..add(ProjectEntity.serializer)
|
..add(ProjectEntity.serializer)
|
||||||
..add(ProjectItemResponse.serializer)
|
..add(ProjectItemResponse.serializer)
|
||||||
..add(ProjectListResponse.serializer)
|
..add(ProjectListResponse.serializer)
|
||||||
|
..add(ProjectState.serializer)
|
||||||
|
..add(ProjectUIState.serializer)
|
||||||
..add(QuoteState.serializer)
|
..add(QuoteState.serializer)
|
||||||
..add(QuoteUIState.serializer)
|
..add(QuoteUIState.serializer)
|
||||||
..add(SizeEntity.serializer)
|
..add(SizeEntity.serializer)
|
||||||
|
|
@ -349,5 +351,7 @@ Serializers _$serializers = (new Serializers().toBuilder()
|
||||||
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(PaymentEntity)]), () => new MapBuilder<int, PaymentEntity>())
|
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(PaymentEntity)]), () => new MapBuilder<int, PaymentEntity>())
|
||||||
..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(ProductEntity)]), () => new MapBuilder<int, ProductEntity>())
|
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(int), const FullType(ProductEntity)]), () => new MapBuilder<int, ProductEntity>())
|
||||||
|
..addBuilderFactory(const FullType(BuiltList, const [const FullType(int)]), () => new ListBuilder<int>())
|
||||||
|
..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>()))
|
||||||
.build();
|
.build();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
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 ProjectRepository {
|
||||||
|
final WebClient webClient;
|
||||||
|
|
||||||
|
const ProjectRepository({
|
||||||
|
this.webClient = const WebClient(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<ProjectEntity> loadItem(
|
||||||
|
CompanyEntity company, AuthState auth, int entityId) async {
|
||||||
|
final dynamic response = await webClient.get(
|
||||||
|
'${auth.url}/projects/$entityId', company.token);
|
||||||
|
|
||||||
|
final ProjectItemResponse projectResponse =
|
||||||
|
serializers.deserializeWith(ProjectItemResponse.serializer, response);
|
||||||
|
|
||||||
|
return projectResponse.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<BuiltList<ProjectEntity>> loadList(
|
||||||
|
CompanyEntity company, AuthState auth, int updatedAt) async {
|
||||||
|
String url = auth.url + '/projects?';
|
||||||
|
|
||||||
|
if (updatedAt > 0) {
|
||||||
|
url += '&updated_at=${updatedAt - kUpdatedAtBufferSeconds}';
|
||||||
|
}
|
||||||
|
|
||||||
|
final dynamic response = await webClient.get(url, company.token);
|
||||||
|
|
||||||
|
final ProjectListResponse projectResponse =
|
||||||
|
serializers.deserializeWith(ProjectListResponse.serializer, response);
|
||||||
|
|
||||||
|
return projectResponse.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ProjectEntity> saveData(
|
||||||
|
CompanyEntity company, AuthState auth, ProjectEntity project,
|
||||||
|
[EntityAction action]) async {
|
||||||
|
final data = serializers.serializeWith(ProjectEntity.serializer, project);
|
||||||
|
dynamic response;
|
||||||
|
|
||||||
|
if (project.isNew) {
|
||||||
|
response = await webClient.post(
|
||||||
|
auth.url + '/projects',
|
||||||
|
company.token,
|
||||||
|
json.encode(data));
|
||||||
|
} else {
|
||||||
|
var url = auth.url + '/projects/' + project.id.toString();
|
||||||
|
if (action != null) {
|
||||||
|
url += '?action=' + action.toString();
|
||||||
|
}
|
||||||
|
response = await webClient.put(url, company.token, json.encode(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
final ProjectItemResponse projectResponse =
|
||||||
|
serializers.deserializeWith(ProjectItemResponse.serializer, response);
|
||||||
|
|
||||||
|
return projectResponse.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -40,6 +40,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/project/project_screen.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/project/edit/project_edit_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/project/view/project_view_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/project/project_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/project/project_middleware.dart';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/ui/payment/payment_screen.dart';
|
import 'package:invoiceninja_flutter/ui/payment/payment_screen.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/payment/edit/payment_edit_vm.dart';
|
import 'package:invoiceninja_flutter/ui/payment/edit/payment_edit_vm.dart';
|
||||||
import 'package:invoiceninja_flutter/ui/payment/view/payment_view_vm.dart';
|
import 'package:invoiceninja_flutter/ui/payment/view/payment_view_vm.dart';
|
||||||
|
|
@ -69,6 +75,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(createStoreProjectsMiddleware())
|
||||||
|
|
||||||
..addAll(createStorePaymentsMiddleware())
|
..addAll(createStorePaymentsMiddleware())
|
||||||
..addAll(createStoreQuotesMiddleware())
|
..addAll(createStoreQuotesMiddleware())
|
||||||
..addAll([
|
..addAll([
|
||||||
|
|
@ -240,6 +248,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
|
||||||
|
ProjectScreen.route: (context) {
|
||||||
|
widget.store.dispatch(LoadProjects());
|
||||||
|
return ProjectScreen();
|
||||||
|
},
|
||||||
|
ProjectViewScreen.route: (context) => ProjectViewScreen(),
|
||||||
|
ProjectEditScreen.route: (context) => ProjectEditScreen(),
|
||||||
|
|
||||||
PaymentScreen.route: (context) {
|
PaymentScreen.route: (context) {
|
||||||
if (widget.store.state.paymentState.isStale) {
|
if (widget.store.state.paymentState.isStale) {
|
||||||
widget.store.dispatch(LoadPayments());
|
widget.store.dispatch(LoadPayments());
|
||||||
|
|
|
||||||
|
|
@ -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/project/project_state.dart';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/payment/payment_state.dart';
|
import 'package:invoiceninja_flutter/redux/payment/payment_state.dart';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
||||||
|
|
@ -103,9 +105,10 @@ 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.project:
|
||||||
|
return projectUIState;
|
||||||
case EntityType.payment:
|
case EntityType.payment:
|
||||||
return paymentUIState;
|
return paymentUIState;
|
||||||
|
|
||||||
case EntityType.quote:
|
case EntityType.quote:
|
||||||
return quoteUIState;
|
return quoteUIState;
|
||||||
|
|
||||||
|
|
@ -137,6 +140,12 @@ 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
|
||||||
|
ProjectState get projectState => selectedCompanyState.projectState;
|
||||||
|
|
||||||
|
ListUIState get projectListState => uiState.projectUIState.listUIState;
|
||||||
|
|
||||||
|
ProjectUIState get projectUIState => uiState.projectUIState;
|
||||||
|
|
||||||
PaymentState get paymentState => selectedCompanyState.paymentState;
|
PaymentState get paymentState => selectedCompanyState.paymentState;
|
||||||
|
|
||||||
ListUIState get paymentListState => uiState.paymentUIState.listUIState;
|
ListUIState get paymentListState => uiState.paymentUIState.listUIState;
|
||||||
|
|
|
||||||
|
|
@ -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/project/project_reducer.dart';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/payment/payment_reducer.dart';
|
import 'package:invoiceninja_flutter/redux/payment/payment_reducer.dart';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_reducer.dart';
|
import 'package:invoiceninja_flutter/redux/quote/quote_reducer.dart';
|
||||||
|
|
@ -25,6 +27,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
|
||||||
|
..projectState.replace(projectsReducer(state.projectState, action))
|
||||||
|
|
||||||
..paymentState.replace(paymentsReducer(state.paymentState, action))
|
..paymentState.replace(paymentsReducer(state.paymentState, action))
|
||||||
|
|
||||||
..quoteState.replace(quotesReducer(state.quoteState, action)));
|
..quoteState.replace(quotesReducer(state.quoteState, 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/project/project_state.dart';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/payment/payment_state.dart';
|
import 'package:invoiceninja_flutter/redux/payment/payment_state.dart';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
||||||
|
|
@ -23,6 +25,8 @@ abstract class CompanyState
|
||||||
clientState: ClientState(),
|
clientState: ClientState(),
|
||||||
invoiceState: InvoiceState(),
|
invoiceState: InvoiceState(),
|
||||||
// STARTER: constructor - do not remove comment
|
// STARTER: constructor - do not remove comment
|
||||||
|
projectState: ProjectState(),
|
||||||
|
|
||||||
paymentState: PaymentState(),
|
paymentState: PaymentState(),
|
||||||
quoteState: QuoteState(),
|
quoteState: QuoteState(),
|
||||||
);
|
);
|
||||||
|
|
@ -42,6 +46,8 @@ abstract class CompanyState
|
||||||
InvoiceState get invoiceState;
|
InvoiceState get invoiceState;
|
||||||
|
|
||||||
// STARTER: fields - do not remove comment
|
// STARTER: fields - do not remove comment
|
||||||
|
ProjectState get projectState;
|
||||||
|
|
||||||
PaymentState get paymentState;
|
PaymentState get paymentState;
|
||||||
|
|
||||||
QuoteState get quoteState;
|
QuoteState get quoteState;
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
|
'projectState',
|
||||||
|
serializers.serialize(object.projectState,
|
||||||
|
specifiedType: const FullType(ProjectState)),
|
||||||
'paymentState',
|
'paymentState',
|
||||||
serializers.serialize(object.paymentState,
|
serializers.serialize(object.paymentState,
|
||||||
specifiedType: const FullType(PaymentState)),
|
specifiedType: const FullType(PaymentState)),
|
||||||
|
|
@ -92,6 +95,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 'projectState':
|
||||||
|
result.projectState.replace(serializers.deserialize(value,
|
||||||
|
specifiedType: const FullType(ProjectState)) as ProjectState);
|
||||||
|
break;
|
||||||
case 'paymentState':
|
case 'paymentState':
|
||||||
result.paymentState.replace(serializers.deserialize(value,
|
result.paymentState.replace(serializers.deserialize(value,
|
||||||
specifiedType: const FullType(PaymentState)) as PaymentState);
|
specifiedType: const FullType(PaymentState)) as PaymentState);
|
||||||
|
|
@ -119,6 +126,8 @@ class _$CompanyState extends CompanyState {
|
||||||
@override
|
@override
|
||||||
final InvoiceState invoiceState;
|
final InvoiceState invoiceState;
|
||||||
@override
|
@override
|
||||||
|
final ProjectState projectState;
|
||||||
|
@override
|
||||||
final PaymentState paymentState;
|
final PaymentState paymentState;
|
||||||
@override
|
@override
|
||||||
final QuoteState quoteState;
|
final QuoteState quoteState;
|
||||||
|
|
@ -132,6 +141,7 @@ class _$CompanyState extends CompanyState {
|
||||||
this.productState,
|
this.productState,
|
||||||
this.clientState,
|
this.clientState,
|
||||||
this.invoiceState,
|
this.invoiceState,
|
||||||
|
this.projectState,
|
||||||
this.paymentState,
|
this.paymentState,
|
||||||
this.quoteState})
|
this.quoteState})
|
||||||
: super._() {
|
: super._() {
|
||||||
|
|
@ -147,6 +157,9 @@ class _$CompanyState extends CompanyState {
|
||||||
if (invoiceState == null) {
|
if (invoiceState == null) {
|
||||||
throw new BuiltValueNullFieldError('CompanyState', 'invoiceState');
|
throw new BuiltValueNullFieldError('CompanyState', 'invoiceState');
|
||||||
}
|
}
|
||||||
|
if (projectState == null) {
|
||||||
|
throw new BuiltValueNullFieldError('CompanyState', 'projectState');
|
||||||
|
}
|
||||||
if (paymentState == null) {
|
if (paymentState == null) {
|
||||||
throw new BuiltValueNullFieldError('CompanyState', 'paymentState');
|
throw new BuiltValueNullFieldError('CompanyState', 'paymentState');
|
||||||
}
|
}
|
||||||
|
|
@ -171,6 +184,7 @@ class _$CompanyState extends CompanyState {
|
||||||
productState == other.productState &&
|
productState == other.productState &&
|
||||||
clientState == other.clientState &&
|
clientState == other.clientState &&
|
||||||
invoiceState == other.invoiceState &&
|
invoiceState == other.invoiceState &&
|
||||||
|
projectState == other.projectState &&
|
||||||
paymentState == other.paymentState &&
|
paymentState == other.paymentState &&
|
||||||
quoteState == other.quoteState;
|
quoteState == other.quoteState;
|
||||||
}
|
}
|
||||||
|
|
@ -181,10 +195,14 @@ class _$CompanyState extends CompanyState {
|
||||||
$jc(
|
$jc(
|
||||||
$jc(
|
$jc(
|
||||||
$jc(
|
$jc(
|
||||||
$jc($jc($jc(0, company.hashCode), dashboardState.hashCode),
|
$jc(
|
||||||
|
$jc(
|
||||||
|
$jc($jc(0, company.hashCode),
|
||||||
|
dashboardState.hashCode),
|
||||||
productState.hashCode),
|
productState.hashCode),
|
||||||
clientState.hashCode),
|
clientState.hashCode),
|
||||||
invoiceState.hashCode),
|
invoiceState.hashCode),
|
||||||
|
projectState.hashCode),
|
||||||
paymentState.hashCode),
|
paymentState.hashCode),
|
||||||
quoteState.hashCode));
|
quoteState.hashCode));
|
||||||
}
|
}
|
||||||
|
|
@ -197,6 +215,7 @@ class _$CompanyState extends CompanyState {
|
||||||
..add('productState', productState)
|
..add('productState', productState)
|
||||||
..add('clientState', clientState)
|
..add('clientState', clientState)
|
||||||
..add('invoiceState', invoiceState)
|
..add('invoiceState', invoiceState)
|
||||||
|
..add('projectState', projectState)
|
||||||
..add('paymentState', paymentState)
|
..add('paymentState', paymentState)
|
||||||
..add('quoteState', quoteState))
|
..add('quoteState', quoteState))
|
||||||
.toString();
|
.toString();
|
||||||
|
|
@ -236,6 +255,12 @@ class CompanyStateBuilder
|
||||||
set invoiceState(InvoiceStateBuilder invoiceState) =>
|
set invoiceState(InvoiceStateBuilder invoiceState) =>
|
||||||
_$this._invoiceState = invoiceState;
|
_$this._invoiceState = invoiceState;
|
||||||
|
|
||||||
|
ProjectStateBuilder _projectState;
|
||||||
|
ProjectStateBuilder get projectState =>
|
||||||
|
_$this._projectState ??= new ProjectStateBuilder();
|
||||||
|
set projectState(ProjectStateBuilder projectState) =>
|
||||||
|
_$this._projectState = projectState;
|
||||||
|
|
||||||
PaymentStateBuilder _paymentState;
|
PaymentStateBuilder _paymentState;
|
||||||
PaymentStateBuilder get paymentState =>
|
PaymentStateBuilder get paymentState =>
|
||||||
_$this._paymentState ??= new PaymentStateBuilder();
|
_$this._paymentState ??= new PaymentStateBuilder();
|
||||||
|
|
@ -257,6 +282,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();
|
||||||
|
_projectState = _$v.projectState?.toBuilder();
|
||||||
_paymentState = _$v.paymentState?.toBuilder();
|
_paymentState = _$v.paymentState?.toBuilder();
|
||||||
_quoteState = _$v.quoteState?.toBuilder();
|
_quoteState = _$v.quoteState?.toBuilder();
|
||||||
_$v = null;
|
_$v = null;
|
||||||
|
|
@ -288,6 +314,7 @@ class CompanyStateBuilder
|
||||||
productState: productState.build(),
|
productState: productState.build(),
|
||||||
clientState: clientState.build(),
|
clientState: clientState.build(),
|
||||||
invoiceState: invoiceState.build(),
|
invoiceState: invoiceState.build(),
|
||||||
|
projectState: projectState.build(),
|
||||||
paymentState: paymentState.build(),
|
paymentState: paymentState.build(),
|
||||||
quoteState: quoteState.build());
|
quoteState: quoteState.build());
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|
@ -303,6 +330,8 @@ class CompanyStateBuilder
|
||||||
clientState.build();
|
clientState.build();
|
||||||
_$failedField = 'invoiceState';
|
_$failedField = 'invoiceState';
|
||||||
invoiceState.build();
|
invoiceState.build();
|
||||||
|
_$failedField = 'projectState';
|
||||||
|
projectState.build();
|
||||||
_$failedField = 'paymentState';
|
_$failedField = 'paymentState';
|
||||||
paymentState.build();
|
paymentState.build();
|
||||||
_$failedField = 'quoteState';
|
_$failedField = 'quoteState';
|
||||||
|
|
|
||||||
|
|
@ -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 ViewProjectList implements PersistUI {
|
||||||
|
ViewProjectList(this.context);
|
||||||
|
|
||||||
|
final BuildContext context;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewProject implements PersistUI {
|
||||||
|
ViewProject({this.projectId, this.context});
|
||||||
|
|
||||||
|
final int projectId;
|
||||||
|
final BuildContext context;
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditProject implements PersistUI {
|
||||||
|
EditProject({this.project, this.context, this.completer, this.trackRoute = true});
|
||||||
|
|
||||||
|
final ProjectEntity project;
|
||||||
|
final BuildContext context;
|
||||||
|
final Completer completer;
|
||||||
|
final bool trackRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateProject implements PersistUI {
|
||||||
|
UpdateProject(this.project);
|
||||||
|
|
||||||
|
final ProjectEntity project;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadProject {
|
||||||
|
LoadProject({this.completer, this.projectId, this.loadActivities = false});
|
||||||
|
|
||||||
|
final Completer completer;
|
||||||
|
final int projectId;
|
||||||
|
final bool loadActivities;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadProjectActivity {
|
||||||
|
LoadProjectActivity({this.completer, this.projectId});
|
||||||
|
|
||||||
|
final Completer completer;
|
||||||
|
final int projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadProjects {
|
||||||
|
LoadProjects({this.completer, this.force = false});
|
||||||
|
|
||||||
|
final Completer completer;
|
||||||
|
final bool force;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadProjectRequest implements StartLoading {}
|
||||||
|
|
||||||
|
class LoadProjectFailure implements StopLoading {
|
||||||
|
LoadProjectFailure(this.error);
|
||||||
|
|
||||||
|
final dynamic error;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'LoadProjectFailure{error: $error}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadProjectSuccess implements StopLoading, PersistData {
|
||||||
|
LoadProjectSuccess(this.project);
|
||||||
|
|
||||||
|
final ProjectEntity project;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'LoadProjectSuccess{project: $project}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadProjectsRequest implements StartLoading {}
|
||||||
|
|
||||||
|
class LoadProjectsFailure implements StopLoading {
|
||||||
|
LoadProjectsFailure(this.error);
|
||||||
|
|
||||||
|
final dynamic error;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'LoadProjectsFailure{error: $error}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoadProjectsSuccess implements StopLoading, PersistData {
|
||||||
|
LoadProjectsSuccess(this.projects);
|
||||||
|
|
||||||
|
final BuiltList<ProjectEntity> projects;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'LoadProjectsSuccess{projects: $projects}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SaveProjectRequest implements StartSaving {
|
||||||
|
SaveProjectRequest({this.completer, this.project});
|
||||||
|
|
||||||
|
final Completer completer;
|
||||||
|
final ProjectEntity project;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SaveProjectSuccess implements StopSaving, PersistData, PersistUI {
|
||||||
|
SaveProjectSuccess(this.project);
|
||||||
|
|
||||||
|
final ProjectEntity project;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddProjectSuccess implements StopSaving, PersistData, PersistUI {
|
||||||
|
AddProjectSuccess(this.project);
|
||||||
|
|
||||||
|
final ProjectEntity project;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SaveProjectFailure implements StopSaving {
|
||||||
|
SaveProjectFailure (this.error);
|
||||||
|
|
||||||
|
final Object error;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArchiveProjectRequest implements StartSaving {
|
||||||
|
ArchiveProjectRequest(this.completer, this.projectId);
|
||||||
|
|
||||||
|
final Completer completer;
|
||||||
|
final int projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArchiveProjectSuccess implements StopSaving, PersistData {
|
||||||
|
ArchiveProjectSuccess(this.project);
|
||||||
|
|
||||||
|
final ProjectEntity project;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArchiveProjectFailure implements StopSaving {
|
||||||
|
ArchiveProjectFailure(this.project);
|
||||||
|
|
||||||
|
final ProjectEntity project;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeleteProjectRequest implements StartSaving {
|
||||||
|
DeleteProjectRequest(this.completer, this.projectId);
|
||||||
|
|
||||||
|
final Completer completer;
|
||||||
|
final int projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeleteProjectSuccess implements StopSaving, PersistData {
|
||||||
|
DeleteProjectSuccess(this.project);
|
||||||
|
|
||||||
|
final ProjectEntity project;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeleteProjectFailure implements StopSaving {
|
||||||
|
DeleteProjectFailure(this.project);
|
||||||
|
|
||||||
|
final ProjectEntity project;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RestoreProjectRequest implements StartSaving {
|
||||||
|
RestoreProjectRequest(this.completer, this.projectId);
|
||||||
|
|
||||||
|
final Completer completer;
|
||||||
|
final int projectId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RestoreProjectSuccess implements StopSaving, PersistData {
|
||||||
|
RestoreProjectSuccess(this.project);
|
||||||
|
|
||||||
|
final ProjectEntity project;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RestoreProjectFailure implements StopSaving {
|
||||||
|
RestoreProjectFailure(this.project);
|
||||||
|
|
||||||
|
final ProjectEntity project;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class FilterProjects {
|
||||||
|
FilterProjects(this.filter);
|
||||||
|
|
||||||
|
final String filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SortProjects implements PersistUI {
|
||||||
|
SortProjects(this.field);
|
||||||
|
|
||||||
|
final String field;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilterProjectsByState implements PersistUI {
|
||||||
|
FilterProjectsByState(this.state);
|
||||||
|
|
||||||
|
final EntityState state;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilterProjectsByCustom1 implements PersistUI {
|
||||||
|
FilterProjectsByCustom1(this.value);
|
||||||
|
|
||||||
|
final String value;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilterProjectsByCustom2 implements PersistUI {
|
||||||
|
FilterProjectsByCustom2(this.value);
|
||||||
|
|
||||||
|
final String value;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FilterProjectsByEntity implements PersistUI {
|
||||||
|
FilterProjectsByEntity({this.entityId, this.entityType});
|
||||||
|
|
||||||
|
final int entityId;
|
||||||
|
final EntityType entityType;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,231 @@
|
||||||
|
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/product/product_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/project/project_screen.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/project/edit/project_edit_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/project/view/project_view_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/project/project_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||||
|
import 'package:invoiceninja_flutter/data/repositories/project_repository.dart';
|
||||||
|
|
||||||
|
List<Middleware<AppState>> createStoreProjectsMiddleware([
|
||||||
|
ProjectRepository repository = const ProjectRepository(),
|
||||||
|
]) {
|
||||||
|
final viewProjectList = _viewProjectList();
|
||||||
|
final viewProject = _viewProject();
|
||||||
|
final editProject = _editProject();
|
||||||
|
final loadProjects = _loadProjects(repository);
|
||||||
|
final loadProject = _loadProject(repository);
|
||||||
|
final saveProject = _saveProject(repository);
|
||||||
|
final archiveProject = _archiveProject(repository);
|
||||||
|
final deleteProject = _deleteProject(repository);
|
||||||
|
final restoreProject = _restoreProject(repository);
|
||||||
|
|
||||||
|
return [
|
||||||
|
TypedMiddleware<AppState, ViewProjectList>(viewProjectList),
|
||||||
|
TypedMiddleware<AppState, ViewProject>(viewProject),
|
||||||
|
TypedMiddleware<AppState, EditProject>(editProject),
|
||||||
|
TypedMiddleware<AppState, LoadProjects>(loadProjects),
|
||||||
|
TypedMiddleware<AppState, LoadProject>(loadProject),
|
||||||
|
TypedMiddleware<AppState, SaveProjectRequest>(saveProject),
|
||||||
|
TypedMiddleware<AppState, ArchiveProjectRequest>(archiveProject),
|
||||||
|
TypedMiddleware<AppState, DeleteProjectRequest>(deleteProject),
|
||||||
|
TypedMiddleware<AppState, RestoreProjectRequest>(restoreProject),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
Middleware<AppState> _editProject() {
|
||||||
|
return (Store<AppState> store, dynamic action, NextDispatcher next) async {
|
||||||
|
next(action);
|
||||||
|
|
||||||
|
final project =
|
||||||
|
await Navigator.of(action.context).pushNamed(ProjectEditScreen.route);
|
||||||
|
|
||||||
|
if (action.completer != null && project != null) {
|
||||||
|
action.completer.complete(project);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Middleware<AppState> _viewProject() {
|
||||||
|
return (Store<AppState> store, dynamic action, NextDispatcher next) async {
|
||||||
|
next(action);
|
||||||
|
|
||||||
|
store.dispatch(UpdateCurrentRoute(ProjectViewScreen.route));
|
||||||
|
Navigator.of(action.context).pushNamed(ProjectViewScreen.route);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Middleware<AppState> _viewProjectList() {
|
||||||
|
return (Store<AppState> store, dynamic action, NextDispatcher next) {
|
||||||
|
next(action);
|
||||||
|
|
||||||
|
store.dispatch(UpdateCurrentRoute(ProjectScreen.route));
|
||||||
|
|
||||||
|
Navigator.of(action.context).pushNamedAndRemoveUntil(ProjectScreen.route, (Route<dynamic> route) => false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Middleware<AppState> _archiveProject(ProjectRepository repository) {
|
||||||
|
return (Store<AppState> store, dynamic action, NextDispatcher next) {
|
||||||
|
final origProject = store.state.projectState.map[action.projectId];
|
||||||
|
repository
|
||||||
|
.saveData(store.state.selectedCompany, store.state.authState,
|
||||||
|
origProject, EntityAction.archive)
|
||||||
|
.then((ProjectEntity project) {
|
||||||
|
store.dispatch(ArchiveProjectSuccess(project));
|
||||||
|
if (action.completer != null) {
|
||||||
|
action.completer.complete(null);
|
||||||
|
}
|
||||||
|
}).catchError((Object error) {
|
||||||
|
print(error);
|
||||||
|
store.dispatch(ArchiveProjectFailure(origProject));
|
||||||
|
if (action.completer != null) {
|
||||||
|
action.completer.completeError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
next(action);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Middleware<AppState> _deleteProject(ProjectRepository repository) {
|
||||||
|
return (Store<AppState> store, dynamic action, NextDispatcher next) {
|
||||||
|
final origProject = store.state.projectState.map[action.projectId];
|
||||||
|
repository
|
||||||
|
.saveData(store.state.selectedCompany, store.state.authState,
|
||||||
|
origProject, EntityAction.delete)
|
||||||
|
.then((ProjectEntity project) {
|
||||||
|
store.dispatch(DeleteProjectSuccess(project));
|
||||||
|
if (action.completer != null) {
|
||||||
|
action.completer.complete(null);
|
||||||
|
}
|
||||||
|
}).catchError((Object error) {
|
||||||
|
print(error);
|
||||||
|
store.dispatch(DeleteProjectFailure(origProject));
|
||||||
|
if (action.completer != null) {
|
||||||
|
action.completer.completeError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
next(action);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Middleware<AppState> _restoreProject(ProjectRepository repository) {
|
||||||
|
return (Store<AppState> store, dynamic action, NextDispatcher next) {
|
||||||
|
final origProject = store.state.projectState.map[action.projectId];
|
||||||
|
repository
|
||||||
|
.saveData(store.state.selectedCompany, store.state.authState,
|
||||||
|
origProject, EntityAction.restore)
|
||||||
|
.then((ProjectEntity project) {
|
||||||
|
store.dispatch(RestoreProjectSuccess(project));
|
||||||
|
if (action.completer != null) {
|
||||||
|
action.completer.complete(null);
|
||||||
|
}
|
||||||
|
}).catchError((Object error) {
|
||||||
|
print(error);
|
||||||
|
store.dispatch(RestoreProjectFailure(origProject));
|
||||||
|
if (action.completer != null) {
|
||||||
|
action.completer.completeError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
next(action);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Middleware<AppState> _saveProject(ProjectRepository repository) {
|
||||||
|
return (Store<AppState> store, dynamic action, NextDispatcher next) {
|
||||||
|
repository
|
||||||
|
.saveData(
|
||||||
|
store.state.selectedCompany, store.state.authState, action.project)
|
||||||
|
.then((ProjectEntity project) {
|
||||||
|
if (action.project.isNew) {
|
||||||
|
store.dispatch(AddProjectSuccess(project));
|
||||||
|
} else {
|
||||||
|
store.dispatch(SaveProjectSuccess(project));
|
||||||
|
}
|
||||||
|
action.completer.complete(project);
|
||||||
|
}).catchError((Object error) {
|
||||||
|
print(error);
|
||||||
|
store.dispatch(SaveProjectFailure(error));
|
||||||
|
action.completer.completeError(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
next(action);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Middleware<AppState> _loadProject(ProjectRepository repository) {
|
||||||
|
return (Store<AppState> store, dynamic action, NextDispatcher next) {
|
||||||
|
final AppState state = store.state;
|
||||||
|
|
||||||
|
if (state.isLoading) {
|
||||||
|
next(action);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.dispatch(LoadProjectRequest());
|
||||||
|
repository
|
||||||
|
.loadItem(state.selectedCompany, state.authState, action.projectId)
|
||||||
|
.then((project) {
|
||||||
|
store.dispatch(LoadProjectSuccess(project));
|
||||||
|
|
||||||
|
if (action.completer != null) {
|
||||||
|
action.completer.complete(null);
|
||||||
|
}
|
||||||
|
}).catchError((Object error) {
|
||||||
|
print(error);
|
||||||
|
store.dispatch(LoadProjectFailure(error));
|
||||||
|
if (action.completer != null) {
|
||||||
|
action.completer.completeError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
next(action);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Middleware<AppState> _loadProjects(ProjectRepository repository) {
|
||||||
|
return (Store<AppState> store, dynamic action, NextDispatcher next) {
|
||||||
|
final AppState state = store.state;
|
||||||
|
|
||||||
|
if (!state.projectState.isStale && !action.force) {
|
||||||
|
next(action);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.isLoading) {
|
||||||
|
next(action);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int updatedAt = (state.projectState.lastUpdated / 1000).round();
|
||||||
|
|
||||||
|
store.dispatch(LoadProjectsRequest());
|
||||||
|
repository
|
||||||
|
.loadList(state.selectedCompany, state.authState, updatedAt)
|
||||||
|
.then((data) {
|
||||||
|
store.dispatch(LoadProjectsSuccess(data));
|
||||||
|
|
||||||
|
if (action.completer != null) {
|
||||||
|
action.completer.complete(null);
|
||||||
|
}
|
||||||
|
if (state.productState.isStale) {
|
||||||
|
store.dispatch(LoadProducts());
|
||||||
|
}
|
||||||
|
}).catchError((Object error) {
|
||||||
|
print(error);
|
||||||
|
store.dispatch(LoadProjectsFailure(error));
|
||||||
|
if (action.completer != null) {
|
||||||
|
action.completer.completeError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
next(action);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -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/project/project_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/project/project_state.dart';
|
||||||
|
|
||||||
|
EntityUIState projectUIReducer(ProjectUIState state, dynamic action) {
|
||||||
|
return state.rebuild((b) => b
|
||||||
|
..listUIState.replace(projectListReducer(state.listUIState, action))
|
||||||
|
..editing.replace(editingReducer(state.editing, action))
|
||||||
|
..selectedId = selectedIdReducer(state.selectedId, action));
|
||||||
|
}
|
||||||
|
|
||||||
|
Reducer<int> selectedIdReducer = combineReducers([
|
||||||
|
TypedReducer<int, ViewProject>(
|
||||||
|
(int selectedId, dynamic action) => action.projectId),
|
||||||
|
TypedReducer<int, AddProjectSuccess>(
|
||||||
|
(int selectedId, dynamic action) => action.project.id),
|
||||||
|
]);
|
||||||
|
|
||||||
|
final editingReducer = combineReducers<ProjectEntity>([
|
||||||
|
TypedReducer<ProjectEntity, SaveProjectSuccess>(_updateEditing),
|
||||||
|
TypedReducer<ProjectEntity, AddProjectSuccess>(_updateEditing),
|
||||||
|
TypedReducer<ProjectEntity, RestoreProjectSuccess>(_updateEditing),
|
||||||
|
TypedReducer<ProjectEntity, ArchiveProjectSuccess>(_updateEditing),
|
||||||
|
TypedReducer<ProjectEntity, DeleteProjectSuccess>(_updateEditing),
|
||||||
|
TypedReducer<ProjectEntity, EditProject>(_updateEditing),
|
||||||
|
TypedReducer<ProjectEntity, UpdateProject>(_updateEditing),
|
||||||
|
TypedReducer<ProjectEntity, SelectCompany>(_clearEditing),
|
||||||
|
]);
|
||||||
|
|
||||||
|
ProjectEntity _clearEditing(ProjectEntity project, dynamic action) {
|
||||||
|
return ProjectEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectEntity _updateEditing(ProjectEntity project, dynamic action) {
|
||||||
|
return action.project;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
final projectListReducer = combineReducers<ListUIState>([
|
||||||
|
TypedReducer<ListUIState, SortProjects>(_sortProjects),
|
||||||
|
TypedReducer<ListUIState, FilterProjectsByState>(_filterProjectsByState),
|
||||||
|
TypedReducer<ListUIState, FilterProjects>(_filterProjects),
|
||||||
|
TypedReducer<ListUIState, FilterProjectsByCustom1>(_filterProjectsByCustom1),
|
||||||
|
TypedReducer<ListUIState, FilterProjectsByCustom2>(_filterProjectsByCustom2),
|
||||||
|
TypedReducer<ListUIState, FilterProjectsByEntity>(_filterProjectsByClient),
|
||||||
|
]);
|
||||||
|
|
||||||
|
ListUIState _filterProjectsByClient(
|
||||||
|
ListUIState projectListState, FilterProjectsByEntity action) {
|
||||||
|
return projectListState.rebuild((b) => b
|
||||||
|
..filterEntityId = action.entityId
|
||||||
|
..filterEntityType = action.entityType);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListUIState _filterProjectsByCustom1(
|
||||||
|
ListUIState projectListState, FilterProjectsByCustom1 action) {
|
||||||
|
if (projectListState.custom1Filters.contains(action.value)) {
|
||||||
|
return projectListState
|
||||||
|
.rebuild((b) => b..custom1Filters.remove(action.value));
|
||||||
|
} else {
|
||||||
|
return projectListState.rebuild((b) => b..custom1Filters.add(action.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListUIState _filterProjectsByCustom2(
|
||||||
|
ListUIState projectListState, FilterProjectsByCustom2 action) {
|
||||||
|
if (projectListState.custom2Filters.contains(action.value)) {
|
||||||
|
return projectListState
|
||||||
|
.rebuild((b) => b..custom2Filters.remove(action.value));
|
||||||
|
} else {
|
||||||
|
return projectListState.rebuild((b) => b..custom2Filters.add(action.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListUIState _filterProjectsByState(
|
||||||
|
ListUIState projectListState, FilterProjectsByState action) {
|
||||||
|
if (projectListState.stateFilters.contains(action.state)) {
|
||||||
|
return projectListState.rebuild((b) => b..stateFilters.remove(action.state));
|
||||||
|
} else {
|
||||||
|
return projectListState.rebuild((b) => b..stateFilters.add(action.state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListUIState _filterProjects(ListUIState projectListState, FilterProjects action) {
|
||||||
|
return projectListState.rebuild((b) => b..filter = action.filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListUIState _sortProjects(ListUIState projectListState, SortProjects action) {
|
||||||
|
return projectListState.rebuild((b) => b
|
||||||
|
..sortAscending = b.sortField != action.field || !b.sortAscending
|
||||||
|
..sortField = action.field);
|
||||||
|
}
|
||||||
|
|
||||||
|
final projectsReducer = combineReducers<ProjectState>([
|
||||||
|
TypedReducer<ProjectState, SaveProjectSuccess>(_updateProject),
|
||||||
|
TypedReducer<ProjectState, AddProjectSuccess>(_addProject),
|
||||||
|
TypedReducer<ProjectState, LoadProjectsSuccess>(_setLoadedProjects),
|
||||||
|
TypedReducer<ProjectState, LoadProjectSuccess>(_setLoadedProject),
|
||||||
|
TypedReducer<ProjectState, ArchiveProjectRequest>(_archiveProjectRequest),
|
||||||
|
TypedReducer<ProjectState, ArchiveProjectSuccess>(_archiveProjectSuccess),
|
||||||
|
TypedReducer<ProjectState, ArchiveProjectFailure>(_archiveProjectFailure),
|
||||||
|
TypedReducer<ProjectState, DeleteProjectRequest>(_deleteProjectRequest),
|
||||||
|
TypedReducer<ProjectState, DeleteProjectSuccess>(_deleteProjectSuccess),
|
||||||
|
TypedReducer<ProjectState, DeleteProjectFailure>(_deleteProjectFailure),
|
||||||
|
TypedReducer<ProjectState, RestoreProjectRequest>(_restoreProjectRequest),
|
||||||
|
TypedReducer<ProjectState, RestoreProjectSuccess>(_restoreProjectSuccess),
|
||||||
|
TypedReducer<ProjectState, RestoreProjectFailure>(_restoreProjectFailure),
|
||||||
|
]);
|
||||||
|
|
||||||
|
ProjectState _archiveProjectRequest(
|
||||||
|
ProjectState projectState, ArchiveProjectRequest action) {
|
||||||
|
final project = projectState.map[action.projectId]
|
||||||
|
.rebuild((b) => b..archivedAt = DateTime.now().millisecondsSinceEpoch);
|
||||||
|
|
||||||
|
return projectState.rebuild((b) => b..map[action.projectId] = project);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState _archiveProjectSuccess(
|
||||||
|
ProjectState projectState, ArchiveProjectSuccess action) {
|
||||||
|
return projectState.rebuild((b) => b..map[action.project.id] = action.project);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState _archiveProjectFailure(
|
||||||
|
ProjectState projectState, ArchiveProjectFailure action) {
|
||||||
|
return projectState.rebuild((b) => b..map[action.project.id] = action.project);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState _deleteProjectRequest(
|
||||||
|
ProjectState projectState, DeleteProjectRequest action) {
|
||||||
|
final project = projectState.map[action.projectId].rebuild((b) => b
|
||||||
|
..archivedAt = DateTime.now().millisecondsSinceEpoch
|
||||||
|
..isDeleted = true);
|
||||||
|
|
||||||
|
return projectState.rebuild((b) => b..map[action.projectId] = project);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState _deleteProjectSuccess(
|
||||||
|
ProjectState projectState, DeleteProjectSuccess action) {
|
||||||
|
return projectState.rebuild((b) => b..map[action.project.id] = action.project);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState _deleteProjectFailure(
|
||||||
|
ProjectState projectState, DeleteProjectFailure action) {
|
||||||
|
return projectState.rebuild((b) => b..map[action.project.id] = action.project);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState _restoreProjectRequest(
|
||||||
|
ProjectState projectState, RestoreProjectRequest action) {
|
||||||
|
final project = projectState.map[action.projectId].rebuild((b) => b
|
||||||
|
..archivedAt = null
|
||||||
|
..isDeleted = false);
|
||||||
|
return projectState.rebuild((b) => b..map[action.projectId] = project);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState _restoreProjectSuccess(
|
||||||
|
ProjectState projectState, RestoreProjectSuccess action) {
|
||||||
|
return projectState.rebuild((b) => b..map[action.project.id] = action.project);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState _restoreProjectFailure(
|
||||||
|
ProjectState projectState, RestoreProjectFailure action) {
|
||||||
|
return projectState.rebuild((b) => b..map[action.project.id] = action.project);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState _addProject(ProjectState projectState, AddProjectSuccess action) {
|
||||||
|
return projectState.rebuild((b) => b
|
||||||
|
..map[action.project.id] = action.project
|
||||||
|
..list.add(action.project.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState _updateProject(ProjectState projectState, SaveProjectSuccess action) {
|
||||||
|
return projectState.rebuild((b) => b
|
||||||
|
..map[action.project.id] = action.project);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState _setLoadedProject(
|
||||||
|
ProjectState projectState, LoadProjectSuccess action) {
|
||||||
|
return projectState.rebuild((b) => b
|
||||||
|
..map[action.project.id] = action.project);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectState _setLoadedProjects(
|
||||||
|
ProjectState projectState, LoadProjectsSuccess action) {
|
||||||
|
final state = projectState.rebuild((b) => b
|
||||||
|
..lastUpdated = DateTime.now().millisecondsSinceEpoch
|
||||||
|
..map.addAll(Map.fromIterable(
|
||||||
|
action.projects,
|
||||||
|
key: (dynamic item) => item.id,
|
||||||
|
value: (dynamic item) => item,
|
||||||
|
)));
|
||||||
|
|
||||||
|
return state.rebuild((b) => b..list.replace(state.map.keys));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
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 memoizedDropdownProjectList = memo2(
|
||||||
|
(BuiltMap<int, ProjectEntity> projectMap, BuiltList<int> projectList) =>
|
||||||
|
dropdownProjectsSelector(projectMap, projectList));
|
||||||
|
|
||||||
|
List<int> dropdownProjectsSelector(
|
||||||
|
BuiltMap<int, ProjectEntity> projectMap, BuiltList<int> projectList) {
|
||||||
|
final list =
|
||||||
|
projectList.where((projectId) => projectMap[projectId].isActive).toList();
|
||||||
|
|
||||||
|
list.sort((projectAId, projectBId) {
|
||||||
|
final projectA = projectMap[projectAId];
|
||||||
|
final projectB = projectMap[projectBId];
|
||||||
|
return projectA.compareTo(projectB, ProjectFields.name, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
var memoizedFilteredProjectList = memo3((BuiltMap<int, ProjectEntity> projectMap,
|
||||||
|
BuiltList<int> projectList, ListUIState projectListState) =>
|
||||||
|
filteredProjectsSelector(projectMap, projectList, projectListState));
|
||||||
|
|
||||||
|
List<int> filteredProjectsSelector(BuiltMap<int, ProjectEntity> projectMap,
|
||||||
|
BuiltList<int> projectList, ListUIState projectListState) {
|
||||||
|
final list = projectList.where((projectId) {
|
||||||
|
final project = projectMap[projectId];
|
||||||
|
if (!project.matchesStates(projectListState.stateFilters)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (projectListState.custom1Filters.isNotEmpty &&
|
||||||
|
!projectListState.custom1Filters.contains(project.customValue1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (projectListState.custom2Filters.isNotEmpty &&
|
||||||
|
!projectListState.custom2Filters.contains(project.customValue2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if (projectListState.filterEntityId != null &&
|
||||||
|
project.entityId != projectListState.filterEntityId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return project.matchesFilter(projectListState.filter);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
list.sort((projectAId, projectBId) {
|
||||||
|
final projectA = projectMap[projectAId];
|
||||||
|
final projectB = projectMap[projectBId];
|
||||||
|
return projectA.compareTo(
|
||||||
|
projectB, projectListState.sortField, projectListState.sortAscending);
|
||||||
|
});
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
@ -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/project_model.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/ui/entity_ui_state.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
|
||||||
|
|
||||||
|
part 'project_state.g.dart';
|
||||||
|
|
||||||
|
abstract class ProjectState implements Built<ProjectState, ProjectStateBuilder> {
|
||||||
|
|
||||||
|
factory ProjectState() {
|
||||||
|
return _$ProjectState._(
|
||||||
|
lastUpdated: 0,
|
||||||
|
map: BuiltMap<int, ProjectEntity>(),
|
||||||
|
list: BuiltList<int>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ProjectState._();
|
||||||
|
|
||||||
|
@nullable
|
||||||
|
int get lastUpdated;
|
||||||
|
|
||||||
|
BuiltMap<int, ProjectEntity> 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<ProjectState> get serializer => _$projectStateSerializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ProjectUIState extends Object with EntityUIState implements Built<ProjectUIState, ProjectUIStateBuilder> {
|
||||||
|
|
||||||
|
factory ProjectUIState() {
|
||||||
|
return _$ProjectUIState._(
|
||||||
|
listUIState: ListUIState(ProjectFields.name),
|
||||||
|
editing: ProjectEntity(),
|
||||||
|
selectedId: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ProjectUIState._();
|
||||||
|
|
||||||
|
@nullable
|
||||||
|
ProjectEntity get editing;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isCreatingNew => editing.isNew;
|
||||||
|
|
||||||
|
static Serializer<ProjectUIState> get serializer => _$projectUIStateSerializer;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,392 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'project_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<ProjectState> _$projectStateSerializer =
|
||||||
|
new _$ProjectStateSerializer();
|
||||||
|
Serializer<ProjectUIState> _$projectUIStateSerializer =
|
||||||
|
new _$ProjectUIStateSerializer();
|
||||||
|
|
||||||
|
class _$ProjectStateSerializer implements StructuredSerializer<ProjectState> {
|
||||||
|
@override
|
||||||
|
final Iterable<Type> types = const [ProjectState, _$ProjectState];
|
||||||
|
@override
|
||||||
|
final String wireName = 'ProjectState';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable serialize(Serializers serializers, ProjectState object,
|
||||||
|
{FullType specifiedType = FullType.unspecified}) {
|
||||||
|
final result = <Object>[
|
||||||
|
'map',
|
||||||
|
serializers.serialize(object.map,
|
||||||
|
specifiedType: const FullType(BuiltMap,
|
||||||
|
const [const FullType(int), const FullType(ProjectEntity)])),
|
||||||
|
'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
|
||||||
|
ProjectState deserialize(Serializers serializers, Iterable serialized,
|
||||||
|
{FullType specifiedType = FullType.unspecified}) {
|
||||||
|
final result = new ProjectStateBuilder();
|
||||||
|
|
||||||
|
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(ProjectEntity)
|
||||||
|
])) 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 _$ProjectUIStateSerializer
|
||||||
|
implements StructuredSerializer<ProjectUIState> {
|
||||||
|
@override
|
||||||
|
final Iterable<Type> types = const [ProjectUIState, _$ProjectUIState];
|
||||||
|
@override
|
||||||
|
final String wireName = 'ProjectUIState';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable serialize(Serializers serializers, ProjectUIState 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(ProjectEntity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProjectUIState deserialize(Serializers serializers, Iterable serialized,
|
||||||
|
{FullType specifiedType = FullType.unspecified}) {
|
||||||
|
final result = new ProjectUIStateBuilder();
|
||||||
|
|
||||||
|
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(ProjectEntity)) as ProjectEntity);
|
||||||
|
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 _$ProjectState extends ProjectState {
|
||||||
|
@override
|
||||||
|
final int lastUpdated;
|
||||||
|
@override
|
||||||
|
final BuiltMap<int, ProjectEntity> map;
|
||||||
|
@override
|
||||||
|
final BuiltList<int> list;
|
||||||
|
|
||||||
|
factory _$ProjectState([void updates(ProjectStateBuilder b)]) =>
|
||||||
|
(new ProjectStateBuilder()..update(updates)).build();
|
||||||
|
|
||||||
|
_$ProjectState._({this.lastUpdated, this.map, this.list}) : super._() {
|
||||||
|
if (map == null) {
|
||||||
|
throw new BuiltValueNullFieldError('ProjectState', 'map');
|
||||||
|
}
|
||||||
|
if (list == null) {
|
||||||
|
throw new BuiltValueNullFieldError('ProjectState', 'list');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProjectState rebuild(void updates(ProjectStateBuilder b)) =>
|
||||||
|
(toBuilder()..update(updates)).build();
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProjectStateBuilder toBuilder() => new ProjectStateBuilder()..replace(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(other, this)) return true;
|
||||||
|
return other is ProjectState &&
|
||||||
|
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('ProjectState')
|
||||||
|
..add('lastUpdated', lastUpdated)
|
||||||
|
..add('map', map)
|
||||||
|
..add('list', list))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProjectStateBuilder
|
||||||
|
implements Builder<ProjectState, ProjectStateBuilder> {
|
||||||
|
_$ProjectState _$v;
|
||||||
|
|
||||||
|
int _lastUpdated;
|
||||||
|
int get lastUpdated => _$this._lastUpdated;
|
||||||
|
set lastUpdated(int lastUpdated) => _$this._lastUpdated = lastUpdated;
|
||||||
|
|
||||||
|
MapBuilder<int, ProjectEntity> _map;
|
||||||
|
MapBuilder<int, ProjectEntity> get map =>
|
||||||
|
_$this._map ??= new MapBuilder<int, ProjectEntity>();
|
||||||
|
set map(MapBuilder<int, ProjectEntity> map) => _$this._map = map;
|
||||||
|
|
||||||
|
ListBuilder<int> _list;
|
||||||
|
ListBuilder<int> get list => _$this._list ??= new ListBuilder<int>();
|
||||||
|
set list(ListBuilder<int> list) => _$this._list = list;
|
||||||
|
|
||||||
|
ProjectStateBuilder();
|
||||||
|
|
||||||
|
ProjectStateBuilder get _$this {
|
||||||
|
if (_$v != null) {
|
||||||
|
_lastUpdated = _$v.lastUpdated;
|
||||||
|
_map = _$v.map?.toBuilder();
|
||||||
|
_list = _$v.list?.toBuilder();
|
||||||
|
_$v = null;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void replace(ProjectState other) {
|
||||||
|
if (other == null) {
|
||||||
|
throw new ArgumentError.notNull('other');
|
||||||
|
}
|
||||||
|
_$v = other as _$ProjectState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(void updates(ProjectStateBuilder b)) {
|
||||||
|
if (updates != null) updates(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
_$ProjectState build() {
|
||||||
|
_$ProjectState _$result;
|
||||||
|
try {
|
||||||
|
_$result = _$v ??
|
||||||
|
new _$ProjectState._(
|
||||||
|
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(
|
||||||
|
'ProjectState', _$failedField, e.toString());
|
||||||
|
}
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
replace(_$result);
|
||||||
|
return _$result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$ProjectUIState extends ProjectUIState {
|
||||||
|
@override
|
||||||
|
final ProjectEntity editing;
|
||||||
|
@override
|
||||||
|
final int selectedId;
|
||||||
|
@override
|
||||||
|
final ListUIState listUIState;
|
||||||
|
|
||||||
|
factory _$ProjectUIState([void updates(ProjectUIStateBuilder b)]) =>
|
||||||
|
(new ProjectUIStateBuilder()..update(updates)).build();
|
||||||
|
|
||||||
|
_$ProjectUIState._({this.editing, this.selectedId, this.listUIState})
|
||||||
|
: super._() {
|
||||||
|
if (selectedId == null) {
|
||||||
|
throw new BuiltValueNullFieldError('ProjectUIState', 'selectedId');
|
||||||
|
}
|
||||||
|
if (listUIState == null) {
|
||||||
|
throw new BuiltValueNullFieldError('ProjectUIState', 'listUIState');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProjectUIState rebuild(void updates(ProjectUIStateBuilder b)) =>
|
||||||
|
(toBuilder()..update(updates)).build();
|
||||||
|
|
||||||
|
@override
|
||||||
|
ProjectUIStateBuilder toBuilder() =>
|
||||||
|
new ProjectUIStateBuilder()..replace(this);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(other, this)) return true;
|
||||||
|
return other is ProjectUIState &&
|
||||||
|
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('ProjectUIState')
|
||||||
|
..add('editing', editing)
|
||||||
|
..add('selectedId', selectedId)
|
||||||
|
..add('listUIState', listUIState))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProjectUIStateBuilder
|
||||||
|
implements Builder<ProjectUIState, ProjectUIStateBuilder> {
|
||||||
|
_$ProjectUIState _$v;
|
||||||
|
|
||||||
|
ProjectEntityBuilder _editing;
|
||||||
|
ProjectEntityBuilder get editing =>
|
||||||
|
_$this._editing ??= new ProjectEntityBuilder();
|
||||||
|
set editing(ProjectEntityBuilder 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;
|
||||||
|
|
||||||
|
ProjectUIStateBuilder();
|
||||||
|
|
||||||
|
ProjectUIStateBuilder get _$this {
|
||||||
|
if (_$v != null) {
|
||||||
|
_editing = _$v.editing?.toBuilder();
|
||||||
|
_selectedId = _$v.selectedId;
|
||||||
|
_listUIState = _$v.listUIState?.toBuilder();
|
||||||
|
_$v = null;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void replace(ProjectUIState other) {
|
||||||
|
if (other == null) {
|
||||||
|
throw new ArgumentError.notNull('other');
|
||||||
|
}
|
||||||
|
_$v = other as _$ProjectUIState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(void updates(ProjectUIStateBuilder b)) {
|
||||||
|
if (updates != null) updates(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
_$ProjectUIState build() {
|
||||||
|
_$ProjectUIState _$result;
|
||||||
|
try {
|
||||||
|
_$result = _$v ??
|
||||||
|
new _$ProjectUIState._(
|
||||||
|
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(
|
||||||
|
'ProjectUIState', _$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/project/project_reducer.dart';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/payment/payment_reducer.dart';
|
import 'package:invoiceninja_flutter/redux/payment/payment_reducer.dart';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_reducer.dart';
|
import 'package:invoiceninja_flutter/redux/quote/quote_reducer.dart';
|
||||||
|
|
@ -28,6 +30,8 @@ UIState uiReducer(UIState state, dynamic action) {
|
||||||
..invoiceUIState.replace(invoiceUIReducer(state.invoiceUIState, action))
|
..invoiceUIState.replace(invoiceUIReducer(state.invoiceUIState, action))
|
||||||
..dashboardUIState.replace(dashboardUIReducer(state.dashboardUIState, action))
|
..dashboardUIState.replace(dashboardUIReducer(state.dashboardUIState, action))
|
||||||
// STARTER: reducer - do not remove comment
|
// STARTER: reducer - do not remove comment
|
||||||
|
..projectUIState.replace(projectUIReducer(state.projectUIState, action))
|
||||||
|
|
||||||
..paymentUIState.replace(paymentUIReducer(state.paymentUIState, action))
|
..paymentUIState.replace(paymentUIReducer(state.paymentUIState, action))
|
||||||
..quoteUIState.replace(quoteUIReducer(state.quoteUIState, action)));
|
..quoteUIState.replace(quoteUIReducer(state.quoteUIState, 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/project/project_state.dart';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/payment/payment_state.dart';
|
import 'package:invoiceninja_flutter/redux/payment/payment_state.dart';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
|
||||||
|
|
@ -28,6 +30,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
|
||||||
|
projectUIState: ProjectUIState(),
|
||||||
|
|
||||||
paymentUIState: PaymentUIState(company),
|
paymentUIState: PaymentUIState(company),
|
||||||
quoteUIState: QuoteUIState(),
|
quoteUIState: QuoteUIState(),
|
||||||
);
|
);
|
||||||
|
|
@ -57,6 +61,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
|
||||||
|
ProjectUIState get projectUIState;
|
||||||
|
|
||||||
PaymentUIState get paymentUIState;
|
PaymentUIState get paymentUIState;
|
||||||
|
|
||||||
QuoteUIState get quoteUIState;
|
QuoteUIState get quoteUIState;
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ class _$UIStateSerializer implements StructuredSerializer<UIState> {
|
||||||
'invoiceUIState',
|
'invoiceUIState',
|
||||||
serializers.serialize(object.invoiceUIState,
|
serializers.serialize(object.invoiceUIState,
|
||||||
specifiedType: const FullType(InvoiceUIState)),
|
specifiedType: const FullType(InvoiceUIState)),
|
||||||
|
'projectUIState',
|
||||||
|
serializers.serialize(object.projectUIState,
|
||||||
|
specifiedType: const FullType(ProjectUIState)),
|
||||||
'paymentUIState',
|
'paymentUIState',
|
||||||
serializers.serialize(object.paymentUIState,
|
serializers.serialize(object.paymentUIState,
|
||||||
specifiedType: const FullType(PaymentUIState)),
|
specifiedType: const FullType(PaymentUIState)),
|
||||||
|
|
@ -127,6 +130,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 'projectUIState':
|
||||||
|
result.projectUIState.replace(serializers.deserialize(value,
|
||||||
|
specifiedType: const FullType(ProjectUIState)) as ProjectUIState);
|
||||||
|
break;
|
||||||
case 'paymentUIState':
|
case 'paymentUIState':
|
||||||
result.paymentUIState.replace(serializers.deserialize(value,
|
result.paymentUIState.replace(serializers.deserialize(value,
|
||||||
specifiedType: const FullType(PaymentUIState)) as PaymentUIState);
|
specifiedType: const FullType(PaymentUIState)) as PaymentUIState);
|
||||||
|
|
@ -164,6 +171,8 @@ class _$UIState extends UIState {
|
||||||
@override
|
@override
|
||||||
final String filter;
|
final String filter;
|
||||||
@override
|
@override
|
||||||
|
final ProjectUIState projectUIState;
|
||||||
|
@override
|
||||||
final PaymentUIState paymentUIState;
|
final PaymentUIState paymentUIState;
|
||||||
@override
|
@override
|
||||||
final QuoteUIState quoteUIState;
|
final QuoteUIState quoteUIState;
|
||||||
|
|
@ -182,6 +191,7 @@ class _$UIState extends UIState {
|
||||||
this.clientUIState,
|
this.clientUIState,
|
||||||
this.invoiceUIState,
|
this.invoiceUIState,
|
||||||
this.filter,
|
this.filter,
|
||||||
|
this.projectUIState,
|
||||||
this.paymentUIState,
|
this.paymentUIState,
|
||||||
this.quoteUIState})
|
this.quoteUIState})
|
||||||
: super._() {
|
: super._() {
|
||||||
|
|
@ -212,6 +222,9 @@ class _$UIState extends UIState {
|
||||||
if (invoiceUIState == null) {
|
if (invoiceUIState == null) {
|
||||||
throw new BuiltValueNullFieldError('UIState', 'invoiceUIState');
|
throw new BuiltValueNullFieldError('UIState', 'invoiceUIState');
|
||||||
}
|
}
|
||||||
|
if (projectUIState == null) {
|
||||||
|
throw new BuiltValueNullFieldError('UIState', 'projectUIState');
|
||||||
|
}
|
||||||
if (paymentUIState == null) {
|
if (paymentUIState == null) {
|
||||||
throw new BuiltValueNullFieldError('UIState', 'paymentUIState');
|
throw new BuiltValueNullFieldError('UIState', 'paymentUIState');
|
||||||
}
|
}
|
||||||
|
|
@ -241,6 +254,7 @@ class _$UIState extends UIState {
|
||||||
clientUIState == other.clientUIState &&
|
clientUIState == other.clientUIState &&
|
||||||
invoiceUIState == other.invoiceUIState &&
|
invoiceUIState == other.invoiceUIState &&
|
||||||
filter == other.filter &&
|
filter == other.filter &&
|
||||||
|
projectUIState == other.projectUIState &&
|
||||||
paymentUIState == other.paymentUIState &&
|
paymentUIState == other.paymentUIState &&
|
||||||
quoteUIState == other.quoteUIState;
|
quoteUIState == other.quoteUIState;
|
||||||
}
|
}
|
||||||
|
|
@ -257,6 +271,7 @@ class _$UIState extends UIState {
|
||||||
$jc(
|
$jc(
|
||||||
$jc(
|
$jc(
|
||||||
$jc(
|
$jc(
|
||||||
|
$jc(
|
||||||
$jc(
|
$jc(
|
||||||
$jc(
|
$jc(
|
||||||
0,
|
0,
|
||||||
|
|
@ -271,6 +286,7 @@ class _$UIState extends UIState {
|
||||||
clientUIState.hashCode),
|
clientUIState.hashCode),
|
||||||
invoiceUIState.hashCode),
|
invoiceUIState.hashCode),
|
||||||
filter.hashCode),
|
filter.hashCode),
|
||||||
|
projectUIState.hashCode),
|
||||||
paymentUIState.hashCode),
|
paymentUIState.hashCode),
|
||||||
quoteUIState.hashCode));
|
quoteUIState.hashCode));
|
||||||
}
|
}
|
||||||
|
|
@ -288,6 +304,7 @@ class _$UIState extends UIState {
|
||||||
..add('clientUIState', clientUIState)
|
..add('clientUIState', clientUIState)
|
||||||
..add('invoiceUIState', invoiceUIState)
|
..add('invoiceUIState', invoiceUIState)
|
||||||
..add('filter', filter)
|
..add('filter', filter)
|
||||||
|
..add('projectUIState', projectUIState)
|
||||||
..add('paymentUIState', paymentUIState)
|
..add('paymentUIState', paymentUIState)
|
||||||
..add('quoteUIState', quoteUIState))
|
..add('quoteUIState', quoteUIState))
|
||||||
.toString();
|
.toString();
|
||||||
|
|
@ -348,6 +365,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;
|
||||||
|
|
||||||
|
ProjectUIStateBuilder _projectUIState;
|
||||||
|
ProjectUIStateBuilder get projectUIState =>
|
||||||
|
_$this._projectUIState ??= new ProjectUIStateBuilder();
|
||||||
|
set projectUIState(ProjectUIStateBuilder projectUIState) =>
|
||||||
|
_$this._projectUIState = projectUIState;
|
||||||
|
|
||||||
PaymentUIStateBuilder _paymentUIState;
|
PaymentUIStateBuilder _paymentUIState;
|
||||||
PaymentUIStateBuilder get paymentUIState =>
|
PaymentUIStateBuilder get paymentUIState =>
|
||||||
_$this._paymentUIState ??= new PaymentUIStateBuilder();
|
_$this._paymentUIState ??= new PaymentUIStateBuilder();
|
||||||
|
|
@ -374,6 +397,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;
|
||||||
|
_projectUIState = _$v.projectUIState?.toBuilder();
|
||||||
_paymentUIState = _$v.paymentUIState?.toBuilder();
|
_paymentUIState = _$v.paymentUIState?.toBuilder();
|
||||||
_quoteUIState = _$v.quoteUIState?.toBuilder();
|
_quoteUIState = _$v.quoteUIState?.toBuilder();
|
||||||
_$v = null;
|
_$v = null;
|
||||||
|
|
@ -410,6 +434,7 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
|
||||||
clientUIState: clientUIState.build(),
|
clientUIState: clientUIState.build(),
|
||||||
invoiceUIState: invoiceUIState.build(),
|
invoiceUIState: invoiceUIState.build(),
|
||||||
filter: filter,
|
filter: filter,
|
||||||
|
projectUIState: projectUIState.build(),
|
||||||
paymentUIState: paymentUIState.build(),
|
paymentUIState: paymentUIState.build(),
|
||||||
quoteUIState: quoteUIState.build());
|
quoteUIState: quoteUIState.build());
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|
@ -424,6 +449,8 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
|
||||||
_$failedField = 'invoiceUIState';
|
_$failedField = 'invoiceUIState';
|
||||||
invoiceUIState.build();
|
invoiceUIState.build();
|
||||||
|
|
||||||
|
_$failedField = 'projectUIState';
|
||||||
|
projectUIState.build();
|
||||||
_$failedField = 'paymentUIState';
|
_$failedField = 'paymentUIState';
|
||||||
paymentUIState.build();
|
paymentUIState.build();
|
||||||
_$failedField = 'quoteUIState';
|
_$failedField = 'quoteUIState';
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,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/project/project_actions.dart';
|
||||||
|
|
||||||
import 'package:invoiceninja_flutter/redux/payment/payment_actions.dart';
|
import 'package:invoiceninja_flutter/redux/payment/payment_actions.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
|
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
@ -186,6 +188,12 @@ class AppDrawer extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// STARTER: menu - do not remove comment
|
// STARTER: menu - do not remove comment
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(Icons.widgets),
|
||||||
|
title: Text('Projects'),
|
||||||
|
onTap: () => store.dispatch(ViewProjectList(context)),
|
||||||
|
),
|
||||||
|
|
||||||
DrawerTile(
|
DrawerTile(
|
||||||
company: company,
|
company: company,
|
||||||
entityType: EntityType.payment,
|
entityType: EntityType.payment,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/project/edit/project_edit_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/buttons/refresh_icon_button.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||||
|
|
||||||
|
class ProjectEdit extends StatefulWidget {
|
||||||
|
final ProjectEditVM viewModel;
|
||||||
|
|
||||||
|
const ProjectEdit({
|
||||||
|
Key key,
|
||||||
|
@required this.viewModel,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ProjectEditState createState() => _ProjectEditState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProjectEditState extends State<ProjectEdit> {
|
||||||
|
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 project = widget.viewModel.project;
|
||||||
|
// 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 project = widget.viewModel.project.rebuild((b) => b
|
||||||
|
// STARTER: set value - do not remove comment
|
||||||
|
);
|
||||||
|
if (project != widget.viewModel.project) {
|
||||||
|
widget.viewModel.onChanged(project);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final viewModel = widget.viewModel;
|
||||||
|
final localization = AppLocalization.of(context);
|
||||||
|
final project = viewModel.project;
|
||||||
|
|
||||||
|
return WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
viewModel.onBackPressed();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(viewModel.project.isNew
|
||||||
|
? localization.newProject
|
||||||
|
: localization.editProject),
|
||||||
|
actions: <Widget>[
|
||||||
|
Builder(builder: (BuildContext context) {
|
||||||
|
RefreshIconButton(
|
||||||
|
icon: Icons.cloud_upload,
|
||||||
|
tooltip: localization.save,
|
||||||
|
isVisible: project.isDeleted,
|
||||||
|
isDirty: project.isNew || project != viewModel.origProject,
|
||||||
|
isSaving: viewModel.isSaving,
|
||||||
|
onPressed: () {
|
||||||
|
if (! _formKey.currentState.validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
viewModel.onSavePressed(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: ListView(
|
||||||
|
children: <Widget>[
|
||||||
|
FormCard(
|
||||||
|
children: <Widget>[
|
||||||
|
// STARTER: widgets - do not remove comment
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
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/project/project_screen.dart';
|
||||||
|
import 'package:redux/redux.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/project/project_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/data/models/project_model.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/project/edit/project_edit.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/icon_message.dart';
|
||||||
|
|
||||||
|
class ProjectEditScreen extends StatelessWidget {
|
||||||
|
static const String route = '/project/edit';
|
||||||
|
ProjectEditScreen({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StoreConnector<AppState, ProjectEditVM>(
|
||||||
|
converter: (Store<AppState> store) {
|
||||||
|
return ProjectEditVM.fromStore(store);
|
||||||
|
},
|
||||||
|
builder: (context, viewModel) {
|
||||||
|
return ProjectEdit(
|
||||||
|
viewModel: viewModel,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProjectEditVM {
|
||||||
|
final ProjectEntity project;
|
||||||
|
final Function(ProjectEntity) onChanged;
|
||||||
|
final Function(BuildContext) onSavePressed;
|
||||||
|
final bool isSaving;
|
||||||
|
final ProjectEntity origProject;
|
||||||
|
final Function onBackPressed;
|
||||||
|
final bool isLoading;
|
||||||
|
|
||||||
|
ProjectEditVM({
|
||||||
|
@required this.project,
|
||||||
|
@required this.onChanged,
|
||||||
|
@required this.isSaving,
|
||||||
|
@required this.origProject,
|
||||||
|
@required this.onSavePressed,
|
||||||
|
@required this.onBackPressed,
|
||||||
|
@required this.isLoading,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ProjectEditVM.fromStore(Store<AppState> store) {
|
||||||
|
final project = store.state.projectUIState.editing;
|
||||||
|
final state = store.state;
|
||||||
|
|
||||||
|
return ProjectEditVM(
|
||||||
|
isLoading: state.isLoading,
|
||||||
|
isSaving: state.isSaving,
|
||||||
|
project: project,
|
||||||
|
origProject: state.projectState.map[project.id],
|
||||||
|
onChanged: (ProjectEntity project) {
|
||||||
|
store.dispatch(UpdateProject(project));
|
||||||
|
},
|
||||||
|
onBackPressed: () {
|
||||||
|
store.dispatch(UpdateCurrentRoute(ProjectScreen.route));
|
||||||
|
},
|
||||||
|
onSavePressed: (BuildContext context) {
|
||||||
|
final Completer<Null> completer = new Completer<Null>();
|
||||||
|
store.dispatch(SaveProjectRequest(completer: completer, project: project));
|
||||||
|
return completer.future.then((_) {
|
||||||
|
/*
|
||||||
|
Scaffold.of(context).showSnackBar(SnackBar(
|
||||||
|
content: IconMessage(
|
||||||
|
message: project.isNew
|
||||||
|
? 'Successfully Created Project'
|
||||||
|
: 'Successfully Updated Project',
|
||||||
|
),
|
||||||
|
duration: Duration(seconds: 3)));
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
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/project/project_list_item.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/project/project_list_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||||
|
|
||||||
|
class ProjectList extends StatelessWidget {
|
||||||
|
final ProjectListVM viewModel;
|
||||||
|
|
||||||
|
const ProjectList({
|
||||||
|
Key key,
|
||||||
|
@required this.viewModel,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (!viewModel.isLoaded) {
|
||||||
|
return LoadingIndicator();
|
||||||
|
} else if (viewModel.projectList.isEmpty) {
|
||||||
|
return Opacity(
|
||||||
|
opacity: 0.5,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
AppLocalization.of(context).noRecordsFound,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _buildListView(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showMenu(BuildContext context, ProjectEntity project) async {
|
||||||
|
if (project == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final user = viewModel.user;
|
||||||
|
final message = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) => SimpleDialog(children: <Widget>[
|
||||||
|
user.canCreate(EntityType.project)
|
||||||
|
? ListTile(
|
||||||
|
leading: Icon(Icons.control_point_duplicate),
|
||||||
|
title: Text(AppLocalization.of(context).clone),
|
||||||
|
onTap: () => viewModel.onEntityAction(
|
||||||
|
context, project, EntityAction.clone),
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
Divider(),
|
||||||
|
user.canEditEntity(project) && !project.isActive
|
||||||
|
? ListTile(
|
||||||
|
leading: Icon(Icons.restore),
|
||||||
|
title: Text(AppLocalization.of(context).restore),
|
||||||
|
onTap: () => viewModel.onEntityAction(
|
||||||
|
context, project, EntityAction.restore),
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
user.canEditEntity(project) && project.isActive
|
||||||
|
? ListTile(
|
||||||
|
leading: Icon(Icons.archive),
|
||||||
|
title: Text(AppLocalization.of(context).archive),
|
||||||
|
onTap: () => viewModel.onEntityAction(
|
||||||
|
context, project, EntityAction.archive),
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
user.canEditEntity(project) && !project.isDeleted
|
||||||
|
? ListTile(
|
||||||
|
leading: Icon(Icons.delete),
|
||||||
|
title: Text(AppLocalization.of(context).delete),
|
||||||
|
onTap: () => viewModel.onEntityAction(
|
||||||
|
context, project, EntityAction.delete),
|
||||||
|
)
|
||||||
|
: Container(),
|
||||||
|
]));
|
||||||
|
if (message != null) {
|
||||||
|
Scaffold.of(context).showSnackBar(SnackBar(
|
||||||
|
content: SnackBarRow(
|
||||||
|
message: message,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListView(BuildContext context) {
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () => viewModel.onRefreshed(context),
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: viewModel.projectList.length,
|
||||||
|
itemBuilder: (BuildContext context, index) {
|
||||||
|
final projectId = viewModel.projectList[index];
|
||||||
|
final project = viewModel.projectMap[projectId];
|
||||||
|
return Column(children: <Widget>[
|
||||||
|
ProjectListItem(
|
||||||
|
user: viewModel.user,
|
||||||
|
filter: viewModel.filter,
|
||||||
|
project: project,
|
||||||
|
onEntityAction: (EntityAction action) {
|
||||||
|
if (action == EntityAction.more) {
|
||||||
|
_showMenu(context, project);
|
||||||
|
} else {
|
||||||
|
viewModel.onEntityAction(context, project, action);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onTap: () => viewModel.onProjectTap(context, project),
|
||||||
|
onLongPress: () => _showMenu(context, project),
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
height: 1.0,
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
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 ProjectListItem extends StatelessWidget {
|
||||||
|
final UserEntity user;
|
||||||
|
final Function(EntityAction) onEntityAction;
|
||||||
|
final GestureTapCallback onTap;
|
||||||
|
final GestureTapCallback onLongPress;
|
||||||
|
//final ValueChanged<bool> onCheckboxChanged;
|
||||||
|
final ProjectEntity project;
|
||||||
|
final String filter;
|
||||||
|
|
||||||
|
static final projectItemKey = (int id) => Key('__project_item_${id}__');
|
||||||
|
|
||||||
|
const ProjectListItem({
|
||||||
|
@required this.user,
|
||||||
|
@required this.onEntityAction,
|
||||||
|
@required this.onTap,
|
||||||
|
@required this.onLongPress,
|
||||||
|
//@required this.onCheckboxChanged,
|
||||||
|
@required this.project,
|
||||||
|
@required this.filter,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final filterMatch = filter != null && filter.isNotEmpty
|
||||||
|
? project.matchesFilterValue(filter)
|
||||||
|
: null;
|
||||||
|
final subtitle = filterMatch ?? project.privateNotes;
|
||||||
|
|
||||||
|
return DismissibleEntity(
|
||||||
|
user: user,
|
||||||
|
entity: project,
|
||||||
|
onEntityAction: onEntityAction,
|
||||||
|
child: ListTile(
|
||||||
|
onTap: onTap,
|
||||||
|
onLongPress: onLongPress,
|
||||||
|
/*
|
||||||
|
leading: Checkbox(
|
||||||
|
//key: NinjaKeys.projectItemCheckbox(project.id),
|
||||||
|
value: true,
|
||||||
|
//onChanged: onCheckboxChanged,
|
||||||
|
onChanged: (value) {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
*/
|
||||||
|
title: Container(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
child: Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
project.name,
|
||||||
|
//key: NinjaKeys.clientItemClientKey(client.id),
|
||||||
|
style: Theme.of(context).textTheme.title,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(formatNumber(project.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(project),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
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/utils/completers.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/localization.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/project/project_selectors.dart';
|
||||||
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/project/project_list.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/project/project_actions.dart';
|
||||||
|
|
||||||
|
class ProjectListBuilder extends StatelessWidget {
|
||||||
|
const ProjectListBuilder({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StoreConnector<AppState, ProjectListVM>(
|
||||||
|
converter: ProjectListVM.fromStore,
|
||||||
|
builder: (context, viewModel) {
|
||||||
|
return ProjectList(
|
||||||
|
viewModel: viewModel,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProjectListVM {
|
||||||
|
final UserEntity user;
|
||||||
|
final List<int> projectList;
|
||||||
|
final BuiltMap<int, ProjectEntity> projectMap;
|
||||||
|
final String filter;
|
||||||
|
final bool isLoading;
|
||||||
|
final bool isLoaded;
|
||||||
|
final Function(BuildContext, ProjectEntity) onProjectTap;
|
||||||
|
final Function(BuildContext, ProjectEntity, DismissDirection) onDismissed;
|
||||||
|
final Function(BuildContext) onRefreshed;
|
||||||
|
final Function(BuildContext, ProjectEntity, EntityAction) onEntityAction;
|
||||||
|
|
||||||
|
ProjectListVM({
|
||||||
|
@required this.user,
|
||||||
|
@required this.projectList,
|
||||||
|
@required this.projectMap,
|
||||||
|
@required this.filter,
|
||||||
|
@required this.isLoading,
|
||||||
|
@required this.isLoaded,
|
||||||
|
@required this.onProjectTap,
|
||||||
|
@required this.onDismissed,
|
||||||
|
@required this.onRefreshed,
|
||||||
|
@required this.onEntityAction,
|
||||||
|
});
|
||||||
|
|
||||||
|
static ProjectListVM 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(LoadProjects(completer: completer, force: true));
|
||||||
|
return completer.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
final state = store.state;
|
||||||
|
|
||||||
|
return ProjectListVM(
|
||||||
|
user: state.user,
|
||||||
|
projectList: memoizedFilteredProjectList(state.projectState.map,
|
||||||
|
state.projectState.list, state.projectListState),
|
||||||
|
projectMap: state.projectState.map,
|
||||||
|
isLoading: state.isLoading,
|
||||||
|
isLoaded: state.projectState.isLoaded,
|
||||||
|
filter: state.projectUIState.listUIState.filter,
|
||||||
|
onProjectTap: (context, project) {
|
||||||
|
store.dispatch(EditProject(project: project, context: context));
|
||||||
|
},
|
||||||
|
onEntityAction: (context, project, action) {
|
||||||
|
switch (action) {
|
||||||
|
case EntityAction.clone:
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
store.dispatch(
|
||||||
|
EditProject(context: context, project: project.clone));
|
||||||
|
break;
|
||||||
|
case EntityAction.restore:
|
||||||
|
store.dispatch(RestoreProjectRequest(
|
||||||
|
popCompleter(
|
||||||
|
context, AppLocalization.of(context).restoredProject),
|
||||||
|
project.id));
|
||||||
|
break;
|
||||||
|
case EntityAction.archive:
|
||||||
|
store.dispatch(ArchiveProjectRequest(
|
||||||
|
popCompleter(
|
||||||
|
context, AppLocalization.of(context).archivedProject),
|
||||||
|
project.id));
|
||||||
|
break;
|
||||||
|
case EntityAction.delete:
|
||||||
|
store.dispatch(DeleteProjectRequest(
|
||||||
|
popCompleter(
|
||||||
|
context, AppLocalization.of(context).deletedProject),
|
||||||
|
project.id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRefreshed: (context) => _handleRefresh(context),
|
||||||
|
onDismissed: (BuildContext context, ProjectEntity project,
|
||||||
|
DismissDirection direction) {
|
||||||
|
final localization = AppLocalization.of(context);
|
||||||
|
if (direction == DismissDirection.endToStart) {
|
||||||
|
if (project.isDeleted || project.isArchived) {
|
||||||
|
store.dispatch(RestoreProjectRequest(
|
||||||
|
snackBarCompleter(context, localization.restoredProject),
|
||||||
|
project.id));
|
||||||
|
} else {
|
||||||
|
store.dispatch(ArchiveProjectRequest(
|
||||||
|
snackBarCompleter(context, localization.archivedProject),
|
||||||
|
project.id));
|
||||||
|
}
|
||||||
|
} else if (direction == DismissDirection.startToEnd) {
|
||||||
|
if (project.isDeleted) {
|
||||||
|
store.dispatch(RestoreProjectRequest(
|
||||||
|
snackBarCompleter(context, localization.restoredProject),
|
||||||
|
project.id));
|
||||||
|
} else {
|
||||||
|
store.dispatch(DeleteProjectRequest(
|
||||||
|
snackBarCompleter(context, localization.deletedProject),
|
||||||
|
project.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
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/project/project_list_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/project/project_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/app_drawer_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/app_bottom_bar.dart';
|
||||||
|
import 'package:invoiceninja_flutter/utils/keys.dart';
|
||||||
|
|
||||||
|
class ProjectScreen extends StatelessWidget {
|
||||||
|
static const String route = '/project';
|
||||||
|
|
||||||
|
@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.project,
|
||||||
|
onFilterChanged: (value) {
|
||||||
|
store.dispatch(FilterProjects(value));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
ListFilterButton(
|
||||||
|
entityType: EntityType.project,
|
||||||
|
onFilterPressed: (String value) {
|
||||||
|
store.dispatch(FilterProjects(value));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
drawer: AppDrawerBuilder(),
|
||||||
|
body: ProjectListBuilder(),
|
||||||
|
bottomNavigationBar: AppBottomBar(
|
||||||
|
entityType: EntityType.project,
|
||||||
|
onSelectedSortField: (value) => store.dispatch(SortProjects(value)),
|
||||||
|
customValues1: company.getCustomFieldValues(CustomFieldType.project1,
|
||||||
|
excludeBlank: true),
|
||||||
|
customValues2: company.getCustomFieldValues(CustomFieldType.project2,
|
||||||
|
excludeBlank: true),
|
||||||
|
onSelectedCustom1: (value) =>
|
||||||
|
store.dispatch(FilterProjectsByCustom1(value)),
|
||||||
|
onSelectedCustom2: (value) =>
|
||||||
|
store.dispatch(FilterProjectsByCustom2(value)),
|
||||||
|
sortFields: [
|
||||||
|
ProjectFields.projectKey,
|
||||||
|
ProjectFields.cost,
|
||||||
|
ProjectFields.updatedAt,
|
||||||
|
],
|
||||||
|
onSelectedState: (EntityState state, value) {
|
||||||
|
store.dispatch(FilterProjectsByState(state));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
||||||
|
floatingActionButton: user.canCreate(EntityType.project)
|
||||||
|
? FloatingActionButton(
|
||||||
|
key: Key(ProjectKeys.projectScreenFABKeyString),
|
||||||
|
backgroundColor: Theme.of(context).primaryColorDark,
|
||||||
|
onPressed: () {
|
||||||
|
store.dispatch(
|
||||||
|
EditProject(project: ProjectEntity(), context: context));
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.add,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
tooltip: localization.newProject,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
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/project/view/project_view_vm.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
|
||||||
|
|
||||||
|
class ProjectView extends StatefulWidget {
|
||||||
|
final ProjectViewVM viewModel;
|
||||||
|
|
||||||
|
const ProjectView({
|
||||||
|
Key key,
|
||||||
|
@required this.viewModel,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ProjectViewState createState() => new _ProjectViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProjectViewState extends State<ProjectView> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final viewModel = widget.viewModel;
|
||||||
|
final project = viewModel.project;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(project.displayName),
|
||||||
|
actions: project.isNew
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit),
|
||||||
|
onPressed: () {
|
||||||
|
viewModel.onEditPressed(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ActionMenuButton(
|
||||||
|
user: viewModel.company.user,
|
||||||
|
isSaving: viewModel.isSaving,
|
||||||
|
entity: project,
|
||||||
|
onSelected: viewModel.onActionSelected,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: FormCard(
|
||||||
|
children: [
|
||||||
|
// STARTER: widgets - do not remove comment
|
||||||
|
]
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:redux/redux.dart';
|
||||||
|
import 'package:flutter_redux/flutter_redux.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/project/project_actions.dart';
|
||||||
|
import 'package:invoiceninja_flutter/data/models/project_model.dart';
|
||||||
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/project/view/project_view.dart';
|
||||||
|
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
|
||||||
|
import 'package:invoiceninja_flutter/ui/app/icon_message.dart';
|
||||||
|
|
||||||
|
class ProjectViewScreen extends StatelessWidget {
|
||||||
|
static const String route = '/project/view';
|
||||||
|
const ProjectViewScreen({Key key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return StoreConnector<AppState, ProjectViewVM>(
|
||||||
|
converter: (Store<AppState> store) {
|
||||||
|
return ProjectViewVM.fromStore(store);
|
||||||
|
},
|
||||||
|
builder: (context, vm) {
|
||||||
|
return ProjectView(
|
||||||
|
viewModel: vm,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProjectViewVM {
|
||||||
|
final ProjectEntity project;
|
||||||
|
final CompanyEntity company;
|
||||||
|
final Function(BuildContext, EntityAction) onActionSelected;
|
||||||
|
final Function(BuildContext) onEditPressed;
|
||||||
|
final bool isSaving;
|
||||||
|
final bool isLoading;
|
||||||
|
final bool isDirty;
|
||||||
|
|
||||||
|
ProjectViewVM({
|
||||||
|
@required this.project,
|
||||||
|
@required this.company,
|
||||||
|
@required this.onActionSelected,
|
||||||
|
@required this.onEditPressed,
|
||||||
|
@required this.isSaving,
|
||||||
|
@required this.isLoading,
|
||||||
|
@required this.isDirty,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ProjectViewVM.fromStore(Store<AppState> store) {
|
||||||
|
final state = store.state;
|
||||||
|
final payment = state.projectState.map[state.projectUIState.selectedId];
|
||||||
|
|
||||||
|
return ProjectViewVM(
|
||||||
|
isLoading: store.state.isLoading,
|
||||||
|
project: project,
|
||||||
|
onEditPressed: (BuildContext context) {
|
||||||
|
store.dispatch(EditProject(project: project, context: context));
|
||||||
|
},
|
||||||
|
onActionSelected: (BuildContext context, EntityAction action) {
|
||||||
|
final localization = AppLocalization.of(context);
|
||||||
|
switch (action) {
|
||||||
|
case EntityAction.archive:
|
||||||
|
store.dispatch(ArchiveProjectRequest(
|
||||||
|
popCompleter(context, localization.archivedProject),
|
||||||
|
project.id));
|
||||||
|
break;
|
||||||
|
case EntityAction.delete:
|
||||||
|
store.dispatch(DeleteProjectRequest(
|
||||||
|
popCompleter(context, localization.deletedProject),
|
||||||
|
project.id));
|
||||||
|
break;
|
||||||
|
case EntityAction.restore:
|
||||||
|
store.dispatch(RestoreProjectRequest(
|
||||||
|
snackBarCompleter(context, localization.restoredProject),
|
||||||
|
project.id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@ class AppLocalization {
|
||||||
|
|
||||||
static final Map<String, Map<String, String>> _localizedValues = {
|
static final Map<String, Map<String, String>> _localizedValues = {
|
||||||
'en': {
|
'en': {
|
||||||
|
'new_project': 'New Project',
|
||||||
'thank_you_for_using_our_app': 'Thank you for using our app!',
|
'thank_you_for_using_our_app': 'Thank you for using our app!',
|
||||||
'if_you_like_it': 'If you like it please',
|
'if_you_like_it': 'If you like it please',
|
||||||
'click_here': 'click here',
|
'click_here': 'click here',
|
||||||
|
|
@ -9850,6 +9851,8 @@ class AppLocalization {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
String get newProject => _localizedValues[locale.languageCode]['new_project'];
|
||||||
|
|
||||||
String get thankYouForUsingOurApp =>
|
String get thankYouForUsingOurApp =>
|
||||||
_localizedValues[locale.languageCode]['thank_you_for_using_our_app'];
|
_localizedValues[locale.languageCode]['thank_you_for_using_our_app'];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:core';
|
import 'dart:core';
|
||||||
import 'package:built_collection/built_collection.dart';
|
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/data/models/serializers.dart';
|
||||||
import 'package:invoiceninja_flutter/redux/auth/auth_state.dart';
|
import 'package:invoiceninja_flutter/redux/auth/auth_state.dart';
|
||||||
import 'package:invoiceninja_flutter/data/models/models.dart';
|
import 'package:invoiceninja_flutter/data/models/models.dart';
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,11 @@ final stubListReducer = combineReducers<ListUIState>([
|
||||||
TypedReducer<ListUIState, FilterStubs>(_filterStubs),
|
TypedReducer<ListUIState, FilterStubs>(_filterStubs),
|
||||||
TypedReducer<ListUIState, FilterStubsByCustom1>(_filterStubsByCustom1),
|
TypedReducer<ListUIState, FilterStubsByCustom1>(_filterStubsByCustom1),
|
||||||
TypedReducer<ListUIState, FilterStubsByCustom2>(_filterStubsByCustom2),
|
TypedReducer<ListUIState, FilterStubsByCustom2>(_filterStubsByCustom2),
|
||||||
TypedReducer<ListUIState, FilterStubsByClient>(_filterStubsByClient),
|
TypedReducer<ListUIState, FilterStubsByEntity>(_filterStubsByClient),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ListUIState _filterStubsByClient(
|
ListUIState _filterStubsByClient(
|
||||||
ListUIState stubListState, FilterStubsByClient action) {
|
ListUIState stubListState, FilterStubsByEntity action) {
|
||||||
return stubListState.rebuild((b) => b
|
return stubListState.rebuild((b) => b
|
||||||
..filterEntityId = action.entityId
|
..filterEntityId = action.entityId
|
||||||
..filterEntityType = action.entityType);
|
..filterEntityType = action.entityType);
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class _StubEditState extends State<StubEdit> {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onChanged() {
|
void _onChanged() {
|
||||||
final stub = widget.viewModel.stub.rebuild((b) => b
|
final stub = widget.viewModel.stub.rebuild((b) => b
|
||||||
// STARTER: set value - do not remove comment
|
// STARTER: set value - do not remove comment
|
||||||
);
|
);
|
||||||
|
|
@ -64,6 +64,7 @@ class _StubEditState extends State<StubEdit> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final viewModel = widget.viewModel;
|
final viewModel = widget.viewModel;
|
||||||
final localization = AppLocalization.of(context);
|
final localization = AppLocalization.of(context);
|
||||||
|
final stub = viewModel.stub;
|
||||||
|
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
|
|
@ -76,18 +77,19 @@ class _StubEditState extends State<StubEdit> {
|
||||||
? localization.newStub
|
? localization.newStub
|
||||||
: localization.editStub),
|
: localization.editStub),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
Builder(builder: (BuildContext context) {
|
RefreshIconButton(
|
||||||
return SaveIconButton(
|
icon: Icons.cloud_upload,
|
||||||
isLoading: viewModel.isLoading,
|
tooltip: localization.save,
|
||||||
|
isVisible: !stub.isDeleted,
|
||||||
|
isDirty: stub.isNew || stub != viewModel.origStub,
|
||||||
|
isSaving: viewModel.isSaving,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (! _formKey.currentState.validate()) {
|
if (! _formKey.currentState.validate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.onSavePressed(context);
|
viewModel.onSavePressed(context);
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Form(
|
body: Form(
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ class StubEditVM {
|
||||||
StubEditVM({
|
StubEditVM({
|
||||||
@required this.stub,
|
@required this.stub,
|
||||||
@required this.onChanged,
|
@required this.onChanged,
|
||||||
|
@required this.isSaving,
|
||||||
|
@required this.origStub,
|
||||||
@required this.onSavePressed,
|
@required this.onSavePressed,
|
||||||
@required this.onBackPressed,
|
@required this.onBackPressed,
|
||||||
@required this.isLoading,
|
@required this.isLoading,
|
||||||
|
|
@ -47,9 +49,12 @@ class StubEditVM {
|
||||||
|
|
||||||
factory StubEditVM.fromStore(Store<AppState> store) {
|
factory StubEditVM.fromStore(Store<AppState> store) {
|
||||||
final stub = store.state.stubUIState.editing;
|
final stub = store.state.stubUIState.editing;
|
||||||
|
final state = store.state;
|
||||||
|
|
||||||
return StubEditVM(
|
return StubEditVM(
|
||||||
isLoading: store.state.isLoading,
|
isLoading: state.isLoading,
|
||||||
|
isSaving: state.isSaving,
|
||||||
|
origStub: state.stubState.map[stub.id],
|
||||||
stub: stub,
|
stub: stub,
|
||||||
onChanged: (StubEntity stub) {
|
onChanged: (StubEntity stub) {
|
||||||
store.dispatch(UpdateStub(stub));
|
store.dispatch(UpdateStub(stub));
|
||||||
|
|
|
||||||
|
|
@ -99,8 +99,13 @@ class StubList extends StatelessWidget {
|
||||||
user: viewModel.user,
|
user: viewModel.user,
|
||||||
filter: viewModel.filter,
|
filter: viewModel.filter,
|
||||||
stub: stub,
|
stub: stub,
|
||||||
onDismissed: (DismissDirection direction) =>
|
onEntityAction: (EntityAction action) {
|
||||||
viewModel.onDismissed(context, stub, direction),
|
if (action == EntityAction.more) {
|
||||||
|
_showMenu(context, stub);
|
||||||
|
} else {
|
||||||
|
viewModel.onEntityAction(context, stub, action);
|
||||||
|
}
|
||||||
|
},
|
||||||
onTap: () => viewModel.onStubTap(context, stub),
|
onTap: () => viewModel.onStubTap(context, stub),
|
||||||
onLongPress: () => _showMenu(context, stub),
|
onLongPress: () => _showMenu(context, stub),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import 'package:invoiceninja_flutter/ui/app/dismissible_entity.dart';
|
||||||
|
|
||||||
class StubListItem extends StatelessWidget {
|
class StubListItem extends StatelessWidget {
|
||||||
final UserEntity user;
|
final UserEntity user;
|
||||||
final DismissDirectionCallback onDismissed;
|
final Function(EntityAction) onEntityAction;
|
||||||
final GestureTapCallback onTap;
|
final GestureTapCallback onTap;
|
||||||
final GestureTapCallback onLongPress;
|
final GestureTapCallback onLongPress;
|
||||||
//final ValueChanged<bool> onCheckboxChanged;
|
//final ValueChanged<bool> onCheckboxChanged;
|
||||||
|
|
@ -18,7 +18,7 @@ class StubListItem extends StatelessWidget {
|
||||||
|
|
||||||
const StubListItem({
|
const StubListItem({
|
||||||
@required this.user,
|
@required this.user,
|
||||||
@required this.onDismissed,
|
@required this.onEntityAction,
|
||||||
@required this.onTap,
|
@required this.onTap,
|
||||||
@required this.onLongPress,
|
@required this.onLongPress,
|
||||||
//@required this.onCheckboxChanged,
|
//@required this.onCheckboxChanged,
|
||||||
|
|
@ -56,12 +56,12 @@ class StubListItem extends StatelessWidget {
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
stub.stubKey,
|
stub.name,
|
||||||
//key: NinjaKeys.clientItemClientKey(client.id),
|
//key: NinjaKeys.clientItemClientKey(client.id),
|
||||||
style: Theme.of(context).textTheme.title,
|
style: Theme.of(context).textTheme.title,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(formatNumber(stub.cost, context),
|
Text(formatNumber(stub.listDisplayAmount, context),
|
||||||
style: Theme.of(context).textTheme.title),
|
style: Theme.of(context).textTheme.title),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue