Recurring invoices

This commit is contained in:
Hillel Coren 2020-09-09 21:29:01 +03:00
parent 4c7293b4ce
commit 4c14f5cb6e
50 changed files with 2630 additions and 184 deletions

View File

@ -91,6 +91,7 @@ abstract class CompanyEntity extends Object
clients: BuiltList<ClientEntity>(),
products: BuiltList<ProductEntity>(),
invoices: BuiltList<InvoiceEntity>(),
recurringInvoices: BuiltList<InvoiceEntity>(),
payments: BuiltList<PaymentEntity>(),
quotes: BuiltList<InvoiceEntity>(),
credits: BuiltList<InvoiceEntity>(),
@ -215,6 +216,9 @@ abstract class CompanyEntity extends Object
BuiltList<InvoiceEntity> get invoices;
@BuiltValueField(wireName: 'recurring_invoices')
BuiltList<InvoiceEntity> get recurringInvoices;
BuiltList<PaymentEntity> get payments;
BuiltList<InvoiceEntity> get quotes;
@ -407,7 +411,6 @@ abstract class CompanyEntity extends Object
bool isModuleEnabled(EntityType entityType) {
// TODO remove this
if ([
EntityType.recurringInvoice,
EntityType.project,
EntityType.task,
EntityType.expense,

View File

@ -155,6 +155,10 @@ class _$CompanyEntitySerializer implements StructuredSerializer<CompanyEntity> {
serializers.serialize(object.invoices,
specifiedType:
const FullType(BuiltList, const [const FullType(InvoiceEntity)])),
'recurring_invoices',
serializers.serialize(object.recurringInvoices,
specifiedType:
const FullType(BuiltList, const [const FullType(InvoiceEntity)])),
'payments',
serializers.serialize(object.payments,
specifiedType:
@ -455,6 +459,12 @@ class _$CompanyEntitySerializer implements StructuredSerializer<CompanyEntity> {
BuiltList, const [const FullType(InvoiceEntity)]))
as BuiltList<Object>);
break;
case 'recurring_invoices':
result.recurringInvoices.replace(serializers.deserialize(value,
specifiedType: const FullType(
BuiltList, const [const FullType(InvoiceEntity)]))
as BuiltList<Object>);
break;
case 'payments':
result.payments.replace(serializers.deserialize(value,
specifiedType: const FullType(
@ -2762,6 +2772,8 @@ class _$CompanyEntity extends CompanyEntity {
@override
final BuiltList<InvoiceEntity> invoices;
@override
final BuiltList<InvoiceEntity> recurringInvoices;
@override
final BuiltList<PaymentEntity> payments;
@override
final BuiltList<InvoiceEntity> quotes;
@ -2853,6 +2865,7 @@ class _$CompanyEntity extends CompanyEntity {
this.clients,
this.products,
this.invoices,
this.recurringInvoices,
this.payments,
this.quotes,
this.credits,
@ -2995,6 +3008,9 @@ class _$CompanyEntity extends CompanyEntity {
if (invoices == null) {
throw new BuiltValueNullFieldError('CompanyEntity', 'invoices');
}
if (recurringInvoices == null) {
throw new BuiltValueNullFieldError('CompanyEntity', 'recurringInvoices');
}
if (payments == null) {
throw new BuiltValueNullFieldError('CompanyEntity', 'payments');
}
@ -3102,6 +3118,7 @@ class _$CompanyEntity extends CompanyEntity {
clients == other.clients &&
products == other.products &&
invoices == other.invoices &&
recurringInvoices == other.recurringInvoices &&
payments == other.payments &&
quotes == other.quotes &&
credits == other.credits &&
@ -3150,7 +3167,7 @@ class _$CompanyEntity extends CompanyEntity {
$jc(
$jc(
$jc(
$jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc(0, enableCustomSurchargeTaxes1.hashCode), enableCustomSurchargeTaxes2.hashCode), enableCustomSurchargeTaxes3.hashCode), enableCustomSurchargeTaxes4.hashCode), sizeId.hashCode), industryId.hashCode), subdomain.hashCode), portalMode.hashCode), portalDomain.hashCode), updateProducts.hashCode), convertProductExchangeRate.hashCode), fillProducts.hashCode), enableProductCost.hashCode), enableProductQuantity.hashCode), defaultQuantity.hashCode), showProductDetails.hashCode), clientCanRegister.hashCode), isLarge.hashCode), enableShopApi.hashCode), plan.hashCode), companyKey.hashCode), firstDayOfWeek.hashCode), firstMonthOfYear.hashCode), numberOfInvoiceTaxRates.hashCode), numberOfItemTaxRates.hashCode), groups.hashCode), activities.hashCode), taxRates.hashCode), taskStatuses.hashCode), taskStatusMap.hashCode), companyGateways.hashCode), expenseCategories.hashCode), expenseCategoryMap.hashCode), users.hashCode), clients.hashCode), products.hashCode), invoices.hashCode), payments.hashCode), quotes.hashCode), credits.hashCode), tasks.hashCode), projects.hashCode), expenses.hashCode),
$jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc(0, enableCustomSurchargeTaxes1.hashCode), enableCustomSurchargeTaxes2.hashCode), enableCustomSurchargeTaxes3.hashCode), enableCustomSurchargeTaxes4.hashCode), sizeId.hashCode), industryId.hashCode), subdomain.hashCode), portalMode.hashCode), portalDomain.hashCode), updateProducts.hashCode), convertProductExchangeRate.hashCode), fillProducts.hashCode), enableProductCost.hashCode), enableProductQuantity.hashCode), defaultQuantity.hashCode), showProductDetails.hashCode), clientCanRegister.hashCode), isLarge.hashCode), enableShopApi.hashCode), plan.hashCode), companyKey.hashCode), firstDayOfWeek.hashCode), firstMonthOfYear.hashCode), numberOfInvoiceTaxRates.hashCode), numberOfItemTaxRates.hashCode), groups.hashCode), activities.hashCode), taxRates.hashCode), taskStatuses.hashCode), taskStatusMap.hashCode), companyGateways.hashCode), expenseCategories.hashCode), expenseCategoryMap.hashCode), users.hashCode), clients.hashCode), products.hashCode), invoices.hashCode), recurringInvoices.hashCode), payments.hashCode), quotes.hashCode), credits.hashCode), tasks.hashCode), projects.hashCode), expenses.hashCode),
vendors.hashCode),
designs.hashCode),
tokens.hashCode),
@ -3212,6 +3229,7 @@ class _$CompanyEntity extends CompanyEntity {
..add('clients', clients)
..add('products', products)
..add('invoices', invoices)
..add('recurringInvoices', recurringInvoices)
..add('payments', payments)
..add('quotes', quotes)
..add('credits', credits)
@ -3432,6 +3450,12 @@ class CompanyEntityBuilder
set invoices(ListBuilder<InvoiceEntity> invoices) =>
_$this._invoices = invoices;
ListBuilder<InvoiceEntity> _recurringInvoices;
ListBuilder<InvoiceEntity> get recurringInvoices =>
_$this._recurringInvoices ??= new ListBuilder<InvoiceEntity>();
set recurringInvoices(ListBuilder<InvoiceEntity> recurringInvoices) =>
_$this._recurringInvoices = recurringInvoices;
ListBuilder<PaymentEntity> _payments;
ListBuilder<PaymentEntity> get payments =>
_$this._payments ??= new ListBuilder<PaymentEntity>();
@ -3597,6 +3621,7 @@ class CompanyEntityBuilder
_clients = _$v.clients?.toBuilder();
_products = _$v.products?.toBuilder();
_invoices = _$v.invoices?.toBuilder();
_recurringInvoices = _$v.recurringInvoices?.toBuilder();
_payments = _$v.payments?.toBuilder();
_quotes = _$v.quotes?.toBuilder();
_credits = _$v.credits?.toBuilder();
@ -3683,6 +3708,7 @@ class CompanyEntityBuilder
clients: clients.build(),
products: products.build(),
invoices: invoices.build(),
recurringInvoices: recurringInvoices.build(),
payments: payments.build(),
quotes: quotes.build(),
credits: credits.build(),
@ -3735,6 +3761,8 @@ class CompanyEntityBuilder
products.build();
_$failedField = 'invoices';
invoices.build();
_$failedField = 'recurringInvoices';
recurringInvoices.build();
_$failedField = 'payments';
payments.build();
_$failedField = 'quotes';

View File

@ -37,7 +37,6 @@ class EntityType extends EnumClass {
static const EntityType gatewayToken = _$gatewayToken;
static const EntityType invoiceItem = _$invoiceItem;
static const EntityType design = _$design;
// STARTER: entity type - do not remove comment
static const EntityType webhook = _$webhook;
static const EntityType token = _$token;

View File

@ -38,7 +38,9 @@ import 'package:invoiceninja_flutter/redux/task/task_state.dart';
import 'package:invoiceninja_flutter/redux/project/project_state.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_state.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_state.dart';
import 'package:invoiceninja_flutter/data/models/webhook_model.dart';
import 'package:invoiceninja_flutter/redux/webhook/webhook_state.dart';
import 'package:invoiceninja_flutter/data/models/token_model.dart';

View File

@ -128,6 +128,8 @@ Serializers _$serializers = (new Serializers().toBuilder()
..add(ProjectUIState.serializer)
..add(QuoteState.serializer)
..add(QuoteUIState.serializer)
..add(RecurringInvoiceState.serializer)
..add(RecurringInvoiceUIState.serializer)
..add(ReportSettingsEntity.serializer)
..add(ReportsUIState.serializer)
..add(SettingsEntity.serializer)
@ -337,6 +339,9 @@ Serializers _$serializers = (new Serializers().toBuilder()
..addBuilderFactory(
const FullType(BuiltList, const [const FullType(InvoiceEntity)]),
() => new ListBuilder<InvoiceEntity>())
..addBuilderFactory(
const FullType(BuiltList, const [const FullType(InvoiceEntity)]),
() => new ListBuilder<InvoiceEntity>())
..addBuilderFactory(
const FullType(BuiltList, const [const FullType(PaymentEntity)]),
() => new ListBuilder<PaymentEntity>())
@ -386,9 +391,7 @@ Serializers _$serializers = (new Serializers().toBuilder()
..addBuilderFactory(
const FullType(BuiltList, const [const FullType(InvoiceEntity)]),
() => new ListBuilder<InvoiceEntity>())
..addBuilderFactory(
const FullType(BuiltList, const [const FullType(InvoiceItemEntity)]),
() => new ListBuilder<InvoiceItemEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(InvoiceItemEntity)]), () => new ListBuilder<InvoiceItemEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(InvitationEntity)]), () => new ListBuilder<InvitationEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(DocumentEntity)]), () => new ListBuilder<DocumentEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(InvoiceHistoryEntity)]), () => new ListBuilder<InvoiceHistoryEntity>())
@ -461,6 +464,8 @@ Serializers _$serializers = (new Serializers().toBuilder()
..addBuilderFactory(const FullType(BuiltList, const [const FullType(String)]), () => new ListBuilder<String>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(InvoiceEntity)]), () => new MapBuilder<String, InvoiceEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(String)]), () => new ListBuilder<String>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(InvoiceEntity)]), () => new MapBuilder<String, InvoiceEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(String)]), () => new ListBuilder<String>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(PaymentEntity)]), () => new MapBuilder<String, PaymentEntity>())
..addBuilderFactory(const FullType(BuiltList, const [const FullType(String)]), () => new ListBuilder<String>())
..addBuilderFactory(const FullType(BuiltMap, const [const FullType(String), const FullType(PaymentTermEntity)]), () => new MapBuilder<String, PaymentTermEntity>())

View File

@ -0,0 +1,74 @@
import 'dart:async';
import 'dart:convert';
import 'dart:core';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/data/models/serializers.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/data/web_client.dart';
class RecurringInvoiceRepository {
const RecurringInvoiceRepository({
this.webClient = const WebClient(),
});
final WebClient webClient;
Future<InvoiceEntity> loadItem(
Credentials credentials, String entityId) async {
final dynamic response = await webClient.get(
'${credentials.url}/recurring_invoices/$entityId', credentials.token);
final InvoiceItemResponse recurringInvoiceResponse = serializers
.deserializeWith(InvoiceItemResponse.serializer, response);
return recurringInvoiceResponse.data;
}
Future<BuiltList<InvoiceEntity>> loadList(
Credentials credentials) async {
final String url = credentials.url + '/recurring_invoices?';
final dynamic response = await webClient.get(url, credentials.token);
final InvoiceListResponse recurringInvoiceResponse = serializers
.deserializeWith(InvoiceListResponse.serializer, response);
return recurringInvoiceResponse.data;
}
Future<List<InvoiceEntity>> bulkAction(
Credentials credentials, List<String> ids, EntityAction action) async {
final url = credentials.url + '/recurring_invoices/bulk';
final dynamic response = await webClient.post(url, credentials.token,
data: json.encode({'ids': ids, 'action': action.toApiParam()}));
final InvoiceListResponse recurringInvoiceResponse = serializers
.deserializeWith(InvoiceListResponse.serializer, response);
return recurringInvoiceResponse.data.toList();
}
Future<InvoiceEntity> saveData(
Credentials credentials, InvoiceEntity recurringInvoice) async {
final data = serializers.serializeWith(
InvoiceEntity.serializer, recurringInvoice);
dynamic response;
if (recurringInvoice.isNew) {
response = await webClient.post(
credentials.url + '/recurring_invoices', credentials.token,
data: json.encode(data));
} else {
final url =
'${credentials.url}/recurring_invoices/${recurringInvoice.id}';
response =
await webClient.put(url, credentials.token, data: json.encode(data));
}
final InvoiceItemResponse recurringInvoiceResponse = serializers
.deserializeWith(InvoiceItemResponse.serializer, response);
return recurringInvoiceResponse.data;
}
}

View File

@ -40,6 +40,8 @@ import 'package:invoiceninja_flutter/utils/web_stub.dart'
if (dart.library.html) 'package:invoiceninja_flutter/utils/web.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_middleware.dart';
import 'package:invoiceninja_flutter/redux/webhook/webhook_middleware.dart';
import 'package:invoiceninja_flutter/redux/token/token_middleware.dart';
import 'package:invoiceninja_flutter/redux/payment_term/payment_term_middleware.dart';
@ -71,6 +73,7 @@ void main({bool isTesting = false}) async {
..addAll(createStoreSettingsMiddleware())
..addAll(createStoreReportsMiddleware())
// STARTER: middleware - do not remove comment
..addAll(createStoreRecurringInvoicesMiddleware())
..addAll(createStoreWebhooksMiddleware())
..addAll(createStoreTokensMiddleware())
..addAll(createStorePaymentTermsMiddleware())

View File

@ -50,6 +50,11 @@ import 'package:local_auth/local_auth.dart';
import 'package:redux/redux.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/ui/recurring_invoice/recurring_invoice_screen.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/edit/recurring_invoice_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/view/recurring_invoice_view_vm.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/recurring_invoice_screen_vm.dart';
import 'package:invoiceninja_flutter/ui/webhook/webhook_screen.dart';
import 'package:invoiceninja_flutter/ui/webhook/edit/webhook_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/webhook/view/webhook_view_vm.dart';
@ -281,6 +286,13 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
QuoteEditScreen.route: (context) => QuoteEditScreen(),
QuoteEmailScreen.route: (context) => QuoteEmailScreen(),
// STARTER: routes - do not remove comment
RecurringInvoiceScreen.route: (context) =>
RecurringInvoiceScreenBuilder(),
RecurringInvoiceViewScreen.route: (context) =>
RecurringInvoiceViewScreen(),
RecurringInvoiceEditScreen.route: (context) =>
RecurringInvoiceEditScreen(),
WebhookScreen.route: (context) => WebhookScreenBuilder(),
WebhookViewScreen.route: (context) => WebhookViewScreen(),
WebhookEditScreen.route: (context) => WebhookEditScreen(),

View File

@ -46,6 +46,8 @@ import 'package:invoiceninja_flutter/utils/dialogs.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/webhook/webhook_actions.dart';
import 'package:invoiceninja_flutter/redux/token/token_actions.dart';
import 'package:invoiceninja_flutter/redux/payment_term/payment_term_actions.dart';
@ -297,6 +299,10 @@ void viewEntitiesByType({
action = ViewGroupList(navigator: navigator);
break;
// STARTER: view list - do not remove comment
case EntityType.recurringInvoice:
store.dispatch(ViewRecurringInvoiceList(navigator: navigator));
break;
case EntityType.webhook:
store.dispatch(ViewWebhookList(navigator: navigator));
break;
@ -471,6 +477,14 @@ void viewEntityById({
));
break;
// STARTER: view - do not remove comment
case EntityType.recurringInvoice:
store.dispatch(ViewRecurringInvoice(
recurringInvoiceId: entityId,
navigator: navigator,
force: force,
));
break;
case EntityType.webhook:
store.dispatch(ViewWebhook(
webhookId: entityId,
@ -636,6 +650,14 @@ void createEntityByType(
));
break;
// STARTER: create type - do not remove comment
case EntityType.recurringInvoice:
store.dispatch(EditRecurringInvoice(
navigator: navigator,
force: force,
recurringInvoice: InvoiceEntity(state: state),
));
break;
case EntityType.webhook:
store.dispatch(EditWebhook(
navigator: navigator,
@ -826,6 +848,15 @@ void createEntity({
));
break;
// STARTER: create - do not remove comment
case EntityType.recurringInvoice:
store.dispatch(EditRecurringInvoice(
navigator: navigator,
recurringInvoice: entity,
force: force,
completer: completer,
));
break;
case EntityType.webhook:
store.dispatch(EditWebhook(
navigator: navigator,
@ -1059,6 +1090,19 @@ void editEntity(
));
break;
// STARTER: edit - do not remove comment
case EntityType.recurringInvoice:
store.dispatch(EditRecurringInvoice(
recurringInvoice: entity,
navigator: navigator,
completer: completer ??
snackBarCompleter<InvoiceEntity>(
context,
entity.isNew
? localization.createdRecurringInvoice
: localization.updatedRecurringInvoice),
));
break;
case EntityType.webhook:
store.dispatch(EditWebhook(
webhook: entity,
@ -1220,6 +1264,10 @@ void handleEntitiesActions(
handleDocumentAction(context, entities, action);
break;
// STARTER: actions - do not remove comment
case EntityType.recurringInvoice:
handleRecurringInvoiceAction(context, entities, action);
break;
case EntityType.webhook:
handleWebhookAction(context, entities, action);
break;

View File

@ -22,6 +22,8 @@ import 'package:invoiceninja_flutter/redux/company/company_reducer.dart';
import 'package:invoiceninja_flutter/redux/static/static_reducer.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/webhook/webhook_actions.dart';
import 'package:invoiceninja_flutter/redux/token/token_actions.dart';
import 'package:invoiceninja_flutter/redux/payment_term/payment_term_actions.dart';
@ -93,6 +95,10 @@ final lastErrorReducer = combineReducers<String>([
return '${action.error}';
}),
// STARTER: errors - do not remove comment
TypedReducer<String, LoadRecurringInvoicesFailure>((state, action) {
return '${action.error}';
}),
TypedReducer<String, LoadWebhooksFailure>((state, action) {
return '${action.error}';
}),

View File

@ -49,6 +49,10 @@ import 'package:invoiceninja_flutter/ui/group/edit/group_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/product/edit/product_edit_vm.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_state.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/edit/recurring_invoice_edit_vm.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_selectors.dart';
import 'package:invoiceninja_flutter/redux/webhook/webhook_state.dart';
import 'package:invoiceninja_flutter/ui/webhook/edit/webhook_edit_vm.dart';
import 'package:invoiceninja_flutter/redux/webhook/webhook_selectors.dart';
@ -217,6 +221,9 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
case EntityType.invoice:
return invoiceState.map;
// STARTER: states switch map - do not remove comment
case EntityType.recurringInvoice:
return recurringInvoiceState.map;
case EntityType.webhook:
return webhookState.map;
case EntityType.token:
@ -284,6 +291,9 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
case EntityType.invoice:
return invoiceState.list;
// STARTER: states switch list - do not remove comment
case EntityType.recurringInvoice:
return recurringInvoiceState.list;
case EntityType.webhook:
return webhookState.list;
case EntityType.token:
@ -330,6 +340,9 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
case EntityType.invoice:
return invoiceUIState;
// STARTER: states switch - do not remove comment
case EntityType.recurringInvoice:
return recurringInvoiceUIState;
case EntityType.webhook:
return webhookUIState;
@ -393,6 +406,13 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
ListUIState get invoiceListState => uiState.invoiceUIState.listUIState;
// STARTER: state getters - do not remove comment
RecurringInvoiceState get recurringInvoiceState =>
userCompanyState.recurringInvoiceState;
ListUIState get recurringInvoiceListState =>
uiState.recurringInvoiceUIState.listUIState;
RecurringInvoiceUIState get recurringInvoiceUIState =>
uiState.recurringInvoiceUIState;
WebhookState get webhookState => userCompanyState.webhookState;
ListUIState get webhookListState => uiState.webhookUIState.listUIState;
@ -525,6 +545,10 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
case CreditEditScreen.route:
return hasCreditChanges(creditUIState.editing, creditState.map);
// STARTER: has changes - do not remove comment
case RecurringInvoiceEditScreen.route:
return hasRecurringInvoiceChanges(
recurringInvoiceUIState.editing, recurringInvoiceState.map);
case WebhookEditScreen.route:
return hasWebhookChanges(webhookUIState.editing, webhookState.map);
case TokenEditScreen.route:

View File

@ -201,16 +201,15 @@ Middleware<AppState> _createRefreshRequest(AuthRepository repository) {
}
final SharedPreferences prefs = await SharedPreferences.getInstance();
final url = formatApiUrl(
prefs.getString(kSharedPrefUrl) ?? state.authState.url);
final url =
formatApiUrl(prefs.getString(kSharedPrefUrl) ?? state.authState.url);
final token =
TokenEntity.unobscureToken(prefs.getString(kSharedPrefToken)) ??
'TOKEN';
final updatedAt = action.clearData
? 0
: ((state.userCompanyState.lastUpdated -
kMillisecondsToRefreshData) /
: ((state.userCompanyState.lastUpdated - kMillisecondsToRefreshData) /
1000)
.round();

View File

@ -18,6 +18,8 @@ import 'package:invoiceninja_flutter/redux/payment/payment_reducer.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_reducer.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_reducer.dart';
import 'package:invoiceninja_flutter/redux/webhook/webhook_reducer.dart';
import 'package:invoiceninja_flutter/redux/token/token_reducer.dart';
import 'package:invoiceninja_flutter/redux/payment_term/payment_term_reducer.dart';
@ -44,6 +46,8 @@ UserCompanyState companyReducer(UserCompanyState state, dynamic action) {
..vendorState.replace(vendorsReducer(state.vendorState, action))
..taskState.replace(tasksReducer(state.taskState, action))
// STARTER: reducer - do not remove comment
..recurringInvoiceState
.replace(recurringInvoicesReducer(state.recurringInvoiceState, action))
..webhookState.replace(webhooksReducer(state.webhookState, action))
..tokenState.replace(tokensReducer(state.tokenState, action))
..paymentTermState

View File

@ -8,6 +8,8 @@ import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_state.dart';
import 'package:invoiceninja_flutter/redux/webhook/webhook_state.dart';
import 'package:invoiceninja_flutter/redux/token/token_state.dart';
import 'package:invoiceninja_flutter/redux/payment_term/payment_term_state.dart';
@ -44,6 +46,8 @@ abstract class UserCompanyState
paymentState: PaymentState(),
quoteState: QuoteState(),
// STARTER: constructor - do not remove comment
recurringInvoiceState: RecurringInvoiceState(),
webhookState: WebhookState(),
tokenState: TokenState(),
paymentTermState: PaymentTermState(),
@ -88,6 +92,8 @@ abstract class UserCompanyState
QuoteState get quoteState;
// STARTER: fields - do not remove comment
RecurringInvoiceState get recurringInvoiceState;
WebhookState get webhookState;
TokenState get tokenState;

View File

@ -55,6 +55,9 @@ class _$UserCompanyStateSerializer
'quoteState',
serializers.serialize(object.quoteState,
specifiedType: const FullType(QuoteState)),
'recurringInvoiceState',
serializers.serialize(object.recurringInvoiceState,
specifiedType: const FullType(RecurringInvoiceState)),
'webhookState',
serializers.serialize(object.webhookState,
specifiedType: const FullType(WebhookState)),
@ -153,6 +156,11 @@ class _$UserCompanyStateSerializer
result.quoteState.replace(serializers.deserialize(value,
specifiedType: const FullType(QuoteState)) as QuoteState);
break;
case 'recurringInvoiceState':
result.recurringInvoiceState.replace(serializers.deserialize(value,
specifiedType: const FullType(RecurringInvoiceState))
as RecurringInvoiceState);
break;
case 'webhookState':
result.webhookState.replace(serializers.deserialize(value,
specifiedType: const FullType(WebhookState)) as WebhookState);
@ -359,6 +367,8 @@ class _$UserCompanyState extends UserCompanyState {
@override
final QuoteState quoteState;
@override
final RecurringInvoiceState recurringInvoiceState;
@override
final WebhookState webhookState;
@override
final TokenState tokenState;
@ -394,6 +404,7 @@ class _$UserCompanyState extends UserCompanyState {
this.projectState,
this.paymentState,
this.quoteState,
this.recurringInvoiceState,
this.webhookState,
this.tokenState,
this.paymentTermState,
@ -437,6 +448,10 @@ class _$UserCompanyState extends UserCompanyState {
if (quoteState == null) {
throw new BuiltValueNullFieldError('UserCompanyState', 'quoteState');
}
if (recurringInvoiceState == null) {
throw new BuiltValueNullFieldError(
'UserCompanyState', 'recurringInvoiceState');
}
if (webhookState == null) {
throw new BuiltValueNullFieldError('UserCompanyState', 'webhookState');
}
@ -492,6 +507,7 @@ class _$UserCompanyState extends UserCompanyState {
projectState == other.projectState &&
paymentState == other.paymentState &&
quoteState == other.quoteState &&
recurringInvoiceState == other.recurringInvoiceState &&
webhookState == other.webhookState &&
tokenState == other.tokenState &&
paymentTermState == other.paymentTermState &&
@ -524,17 +540,17 @@ class _$UserCompanyState extends UserCompanyState {
$jc(
$jc(
$jc(
$jc($jc($jc(0, lastUpdated.hashCode), userCompany.hashCode),
documentState.hashCode),
productState.hashCode),
clientState.hashCode),
invoiceState.hashCode),
expenseState.hashCode),
vendorState.hashCode),
taskState.hashCode),
projectState.hashCode),
paymentState.hashCode),
quoteState.hashCode),
$jc($jc($jc($jc(0, lastUpdated.hashCode), userCompany.hashCode), documentState.hashCode),
productState.hashCode),
clientState.hashCode),
invoiceState.hashCode),
expenseState.hashCode),
vendorState.hashCode),
taskState.hashCode),
projectState.hashCode),
paymentState.hashCode),
quoteState.hashCode),
recurringInvoiceState.hashCode),
webhookState.hashCode),
tokenState.hashCode),
paymentTermState.hashCode),
@ -561,6 +577,7 @@ class _$UserCompanyState extends UserCompanyState {
..add('projectState', projectState)
..add('paymentState', paymentState)
..add('quoteState', quoteState)
..add('recurringInvoiceState', recurringInvoiceState)
..add('webhookState', webhookState)
..add('tokenState', tokenState)
..add('paymentTermState', paymentTermState)
@ -647,6 +664,13 @@ class UserCompanyStateBuilder
set quoteState(QuoteStateBuilder quoteState) =>
_$this._quoteState = quoteState;
RecurringInvoiceStateBuilder _recurringInvoiceState;
RecurringInvoiceStateBuilder get recurringInvoiceState =>
_$this._recurringInvoiceState ??= new RecurringInvoiceStateBuilder();
set recurringInvoiceState(
RecurringInvoiceStateBuilder recurringInvoiceState) =>
_$this._recurringInvoiceState = recurringInvoiceState;
WebhookStateBuilder _webhookState;
WebhookStateBuilder get webhookState =>
_$this._webhookState ??= new WebhookStateBuilder();
@ -716,6 +740,7 @@ class UserCompanyStateBuilder
_projectState = _$v.projectState?.toBuilder();
_paymentState = _$v.paymentState?.toBuilder();
_quoteState = _$v.quoteState?.toBuilder();
_recurringInvoiceState = _$v.recurringInvoiceState?.toBuilder();
_webhookState = _$v.webhookState?.toBuilder();
_tokenState = _$v.tokenState?.toBuilder();
_paymentTermState = _$v.paymentTermState?.toBuilder();
@ -761,6 +786,7 @@ class UserCompanyStateBuilder
projectState: projectState.build(),
paymentState: paymentState.build(),
quoteState: quoteState.build(),
recurringInvoiceState: recurringInvoiceState.build(),
webhookState: webhookState.build(),
tokenState: tokenState.build(),
paymentTermState: paymentTermState.build(),
@ -795,6 +821,8 @@ class UserCompanyStateBuilder
paymentState.build();
_$failedField = 'quoteState';
quoteState.build();
_$failedField = 'recurringInvoiceState';
recurringInvoiceState.build();
_$failedField = 'webhookState';
webhookState.build();
_$failedField = 'tokenState';

View File

@ -151,12 +151,13 @@ List<String> filteredInvoicesSelector(
list.sort((invoiceAId, invoiceBId) {
return invoiceMap[invoiceAId].compareTo(
invoice: invoiceMap[invoiceBId],
sortField: invoiceListState.sortField,
sortAscending: invoiceListState.sortAscending,
clientMap: clientMap,
staticState: staticState,
userMap: userMap);
invoice: invoiceMap[invoiceBId],
sortField: invoiceListState.sortField,
sortAscending: invoiceListState.sortAscending,
clientMap: clientMap,
staticState: staticState,
userMap: userMap,
);
});
return list;

View File

@ -0,0 +1,330 @@
import 'dart:async';
import 'package:built_collection/built_collection.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class ViewRecurringInvoiceList extends AbstractNavigatorAction
implements PersistUI, StopLoading {
ViewRecurringInvoiceList({
@required NavigatorState navigator,
this.force = false,
}) : super(navigator: navigator);
final bool force;
}
class ViewRecurringInvoice extends AbstractNavigatorAction
implements PersistUI, PersistPrefs {
ViewRecurringInvoice({
@required NavigatorState navigator,
@required this.recurringInvoiceId,
this.force = false,
}) : super(navigator: navigator);
final String recurringInvoiceId;
final bool force;
}
class EditRecurringInvoice extends AbstractNavigatorAction
implements PersistUI, PersistPrefs {
EditRecurringInvoice(
{@required this.recurringInvoice,
@required NavigatorState navigator,
this.completer,
this.cancelCompleter,
this.force = false})
: super(navigator: navigator);
final InvoiceEntity recurringInvoice;
final Completer completer;
final Completer cancelCompleter;
final bool force;
}
class UpdateRecurringInvoice implements PersistUI {
UpdateRecurringInvoice(this.recurringInvoice);
final InvoiceEntity recurringInvoice;
}
class LoadRecurringInvoice {
LoadRecurringInvoice({this.completer, this.recurringInvoiceId});
final Completer completer;
final String recurringInvoiceId;
}
class LoadRecurringInvoiceActivity {
LoadRecurringInvoiceActivity({this.completer, this.recurringInvoiceId});
final Completer completer;
final String recurringInvoiceId;
}
class LoadRecurringInvoices {
LoadRecurringInvoices({this.completer});
final Completer completer;
}
class LoadRecurringInvoiceRequest implements StartLoading {}
class LoadRecurringInvoiceFailure implements StopLoading {
LoadRecurringInvoiceFailure(this.error);
final dynamic error;
@override
String toString() {
return 'LoadRecurringInvoiceFailure{error: $error}';
}
}
class LoadRecurringInvoiceSuccess implements StopLoading, PersistData {
LoadRecurringInvoiceSuccess(this.recurringInvoice);
final InvoiceEntity recurringInvoice;
@override
String toString() {
return 'LoadRecurringInvoiceSuccess{recurringInvoice: $recurringInvoice}';
}
}
class LoadRecurringInvoicesRequest implements StartLoading {}
class LoadRecurringInvoicesFailure implements StopLoading {
LoadRecurringInvoicesFailure(this.error);
final dynamic error;
@override
String toString() {
return 'LoadRecurringInvoicesFailure{error: $error}';
}
}
class LoadRecurringInvoicesSuccess implements StopLoading {
LoadRecurringInvoicesSuccess(this.recurringInvoices);
final BuiltList<InvoiceEntity> recurringInvoices;
@override
String toString() {
return 'LoadRecurringInvoicesSuccess{recurringInvoices: $recurringInvoices}';
}
}
class SaveRecurringInvoiceRequest implements StartSaving {
SaveRecurringInvoiceRequest({this.completer, this.recurringInvoice});
final Completer completer;
final InvoiceEntity recurringInvoice;
}
class SaveRecurringInvoiceSuccess
implements StopSaving, PersistData, PersistUI {
SaveRecurringInvoiceSuccess(this.recurringInvoice);
final InvoiceEntity recurringInvoice;
}
class AddRecurringInvoiceSuccess implements StopSaving, PersistData, PersistUI {
AddRecurringInvoiceSuccess(this.recurringInvoice);
final InvoiceEntity recurringInvoice;
}
class SaveRecurringInvoiceFailure implements StopSaving {
SaveRecurringInvoiceFailure(this.error);
final Object error;
}
class ArchiveRecurringInvoicesRequest implements StartSaving {
ArchiveRecurringInvoicesRequest(this.completer, this.recurringInvoiceIds);
final Completer completer;
final List<String> recurringInvoiceIds;
}
class ArchiveRecurringInvoicesSuccess implements StopSaving, PersistData {
ArchiveRecurringInvoicesSuccess(this.recurringInvoices);
final List<InvoiceEntity> recurringInvoices;
}
class ArchiveRecurringInvoicesFailure implements StopSaving {
ArchiveRecurringInvoicesFailure(this.recurringInvoices);
final List<InvoiceEntity> recurringInvoices;
}
class DeleteRecurringInvoicesRequest implements StartSaving {
DeleteRecurringInvoicesRequest(this.completer, this.recurringInvoiceIds);
final Completer completer;
final List<String> recurringInvoiceIds;
}
class DeleteRecurringInvoicesSuccess implements StopSaving, PersistData {
DeleteRecurringInvoicesSuccess(this.recurringInvoices);
final List<InvoiceEntity> recurringInvoices;
}
class DeleteRecurringInvoicesFailure implements StopSaving {
DeleteRecurringInvoicesFailure(this.recurringInvoices);
final List<InvoiceEntity> recurringInvoices;
}
class RestoreRecurringInvoicesRequest implements StartSaving {
RestoreRecurringInvoicesRequest(this.completer, this.recurringInvoiceIds);
final Completer completer;
final List<String> recurringInvoiceIds;
}
class RestoreRecurringInvoicesSuccess implements StopSaving, PersistData {
RestoreRecurringInvoicesSuccess(this.recurringInvoices);
final List<InvoiceEntity> recurringInvoices;
}
class RestoreRecurringInvoicesFailure implements StopSaving {
RestoreRecurringInvoicesFailure(this.recurringInvoices);
final List<InvoiceEntity> recurringInvoices;
}
class FilterRecurringInvoices implements PersistUI {
FilterRecurringInvoices(this.filter);
final String filter;
}
class SortRecurringInvoices implements PersistUI {
SortRecurringInvoices(this.field);
final String field;
}
class FilterRecurringInvoicesByState implements PersistUI {
FilterRecurringInvoicesByState(this.state);
final EntityState state;
}
class FilterRecurringInvoicesByCustom1 implements PersistUI {
FilterRecurringInvoicesByCustom1(this.value);
final String value;
}
class FilterRecurringInvoicesByCustom2 implements PersistUI {
FilterRecurringInvoicesByCustom2(this.value);
final String value;
}
class FilterRecurringInvoicesByCustom3 implements PersistUI {
FilterRecurringInvoicesByCustom3(this.value);
final String value;
}
class FilterRecurringInvoicesByCustom4 implements PersistUI {
FilterRecurringInvoicesByCustom4(this.value);
final String value;
}
void handleRecurringInvoiceAction(BuildContext context,
List<BaseEntity> recurringInvoices, EntityAction action) {
if (recurringInvoices.isEmpty) {
return;
}
final store = StoreProvider.of<AppState>(context);
final localization = AppLocalization.of(context);
final recurringInvoice = recurringInvoices.first as InvoiceEntity;
final recurringInvoiceIds =
recurringInvoices.map((recurringInvoice) => recurringInvoice.id).toList();
switch (action) {
case EntityAction.edit:
editEntity(context: context, entity: recurringInvoice);
break;
case EntityAction.restore:
store.dispatch(RestoreRecurringInvoicesRequest(
snackBarCompleter<Null>(
context, localization.restoredRecurringInvoice),
recurringInvoiceIds));
break;
case EntityAction.archive:
store.dispatch(ArchiveRecurringInvoicesRequest(
snackBarCompleter<Null>(
context, localization.archivedRecurringInvoice),
recurringInvoiceIds));
break;
case EntityAction.delete:
store.dispatch(DeleteRecurringInvoicesRequest(
snackBarCompleter<Null>(
context, localization.deletedRecurringInvoice),
recurringInvoiceIds));
break;
case EntityAction.toggleMultiselect:
if (!store.state.recurringInvoiceListState.isInMultiselect()) {
store.dispatch(StartRecurringInvoiceMultiselect());
}
if (recurringInvoices.isEmpty) {
break;
}
for (final recurringInvoice in recurringInvoices) {
if (!store.state.recurringInvoiceListState
.isSelected(recurringInvoice.id)) {
store.dispatch(
AddToRecurringInvoiceMultiselect(entity: recurringInvoice));
} else {
store.dispatch(
RemoveFromRecurringInvoiceMultiselect(entity: recurringInvoice));
}
}
break;
case EntityAction.more:
showEntityActionsDialog(
entities: [recurringInvoice],
context: context,
);
break;
}
}
class StartRecurringInvoiceMultiselect {
StartRecurringInvoiceMultiselect();
}
class AddToRecurringInvoiceMultiselect {
AddToRecurringInvoiceMultiselect({@required this.entity});
final BaseEntity entity;
}
class RemoveFromRecurringInvoiceMultiselect {
RemoveFromRecurringInvoiceMultiselect({@required this.entity});
final BaseEntity entity;
}
class ClearRecurringInvoiceMultiselect {
ClearRecurringInvoiceMultiselect();
}

View File

@ -0,0 +1,254 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/recurring_invoice_screen.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/edit/recurring_invoice_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/view/recurring_invoice_view_vm.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/repositories/recurring_invoice_repository.dart';
List<Middleware<AppState>> createStoreRecurringInvoicesMiddleware([
RecurringInvoiceRepository repository = const RecurringInvoiceRepository(),
]) {
final viewRecurringInvoiceList = _viewRecurringInvoiceList();
final viewRecurringInvoice = _viewRecurringInvoice();
final editRecurringInvoice = _editRecurringInvoice();
final loadRecurringInvoices = _loadRecurringInvoices(repository);
final loadRecurringInvoice = _loadRecurringInvoice(repository);
final saveRecurringInvoice = _saveRecurringInvoice(repository);
final archiveRecurringInvoice = _archiveRecurringInvoice(repository);
final deleteRecurringInvoice = _deleteRecurringInvoice(repository);
final restoreRecurringInvoice = _restoreRecurringInvoice(repository);
return [
TypedMiddleware<AppState, ViewRecurringInvoiceList>(
viewRecurringInvoiceList),
TypedMiddleware<AppState, ViewRecurringInvoice>(viewRecurringInvoice),
TypedMiddleware<AppState, EditRecurringInvoice>(editRecurringInvoice),
TypedMiddleware<AppState, LoadRecurringInvoices>(loadRecurringInvoices),
TypedMiddleware<AppState, LoadRecurringInvoice>(loadRecurringInvoice),
TypedMiddleware<AppState, SaveRecurringInvoiceRequest>(
saveRecurringInvoice),
TypedMiddleware<AppState, ArchiveRecurringInvoicesRequest>(
archiveRecurringInvoice),
TypedMiddleware<AppState, DeleteRecurringInvoicesRequest>(
deleteRecurringInvoice),
TypedMiddleware<AppState, RestoreRecurringInvoicesRequest>(
restoreRecurringInvoice),
];
}
Middleware<AppState> _editRecurringInvoice() {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as EditRecurringInvoice;
next(action);
store.dispatch(UpdateCurrentRoute(RecurringInvoiceEditScreen.route));
if (isMobile(action.context)) {
action.navigator.pushNamed(RecurringInvoiceEditScreen.route);
}
};
}
Middleware<AppState> _viewRecurringInvoice() {
return (Store<AppState> store, dynamic dynamicAction,
NextDispatcher next) async {
final action = dynamicAction as ViewRecurringInvoice;
next(action);
store.dispatch(UpdateCurrentRoute(RecurringInvoiceViewScreen.route));
if (isMobile(action.context)) {
Navigator.of(action.context).pushNamed(RecurringInvoiceViewScreen.route);
}
};
}
Middleware<AppState> _viewRecurringInvoiceList() {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as ViewRecurringInvoiceList;
next(action);
if (store.state.staticState.isStale) {
store.dispatch(RefreshData());
}
store.dispatch(UpdateCurrentRoute(RecurringInvoiceScreen.route));
if (isMobile(action.context)) {
Navigator.of(action.context).pushNamedAndRemoveUntil(
RecurringInvoiceScreen.route, (Route<dynamic> route) => false);
}
};
}
Middleware<AppState> _archiveRecurringInvoice(
RecurringInvoiceRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as ArchiveRecurringInvoicesRequest;
final prevRecurringInvoices = action.recurringInvoiceIds
.map((id) => store.state.recurringInvoiceState.map[id])
.toList();
repository
.bulkAction(store.state.credentials, action.recurringInvoiceIds,
EntityAction.archive)
.then((List<InvoiceEntity> recurringInvoices) {
store.dispatch(ArchiveRecurringInvoicesSuccess(recurringInvoices));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(ArchiveRecurringInvoicesFailure(prevRecurringInvoices));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _deleteRecurringInvoice(
RecurringInvoiceRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as DeleteRecurringInvoicesRequest;
final prevRecurringInvoices = action.recurringInvoiceIds
.map((id) => store.state.recurringInvoiceState.map[id])
.toList();
repository
.bulkAction(store.state.credentials, action.recurringInvoiceIds,
EntityAction.delete)
.then((List<InvoiceEntity> recurringInvoices) {
store.dispatch(DeleteRecurringInvoicesSuccess(recurringInvoices));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(DeleteRecurringInvoicesFailure(prevRecurringInvoices));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _restoreRecurringInvoice(
RecurringInvoiceRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as RestoreRecurringInvoicesRequest;
final prevRecurringInvoices = action.recurringInvoiceIds
.map((id) => store.state.recurringInvoiceState.map[id])
.toList();
repository
.bulkAction(store.state.credentials, action.recurringInvoiceIds,
EntityAction.restore)
.then((List<InvoiceEntity> recurringInvoices) {
store.dispatch(RestoreRecurringInvoicesSuccess(recurringInvoices));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(RestoreRecurringInvoicesFailure(prevRecurringInvoices));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _saveRecurringInvoice(
RecurringInvoiceRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as SaveRecurringInvoiceRequest;
repository
.saveData(store.state.credentials, action.recurringInvoice)
.then((InvoiceEntity recurringInvoice) {
if (action.recurringInvoice.isNew) {
store.dispatch(AddRecurringInvoiceSuccess(recurringInvoice));
} else {
store.dispatch(SaveRecurringInvoiceSuccess(recurringInvoice));
}
action.completer.complete(recurringInvoice);
}).catchError((Object error) {
print(error);
store.dispatch(SaveRecurringInvoiceFailure(error));
action.completer.completeError(error);
});
next(action);
};
}
Middleware<AppState> _loadRecurringInvoice(
RecurringInvoiceRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as LoadRecurringInvoice;
final AppState state = store.state;
store.dispatch(LoadRecurringInvoiceRequest());
repository
.loadItem(state.credentials, action.recurringInvoiceId)
.then((recurringInvoice) {
store.dispatch(LoadRecurringInvoiceSuccess(recurringInvoice));
if (action.completer != null) {
action.completer.complete(null);
}
}).catchError((Object error) {
print(error);
store.dispatch(LoadRecurringInvoiceFailure(error));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}
Middleware<AppState> _loadRecurringInvoices(
RecurringInvoiceRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as LoadRecurringInvoices;
final AppState state = store.state;
store.dispatch(LoadRecurringInvoicesRequest());
repository.loadList(state.credentials).then((data) {
store.dispatch(LoadRecurringInvoicesSuccess(data));
if (action.completer != null) {
action.completer.complete(null);
}
/*
if (state.productState.isStale) {
store.dispatch(LoadProducts());
}
*/
}).catchError((Object error) {
print(error);
store.dispatch(LoadRecurringInvoicesFailure(error));
if (action.completer != null) {
action.completer.completeError(error);
}
});
next(action);
};
}

View File

@ -0,0 +1,243 @@
import 'package:redux/redux.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.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/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_state.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart';
EntityUIState recurringInvoiceUIReducer(
RecurringInvoiceUIState state, dynamic action) {
return state.rebuild((b) => b
..listUIState
.replace(recurringInvoiceListReducer(state.listUIState, action))
..editing.replace(editingReducer(state.editing, action))
..selectedId = selectedIdReducer(state.selectedId, action));
}
Reducer<String> selectedIdReducer = combineReducers([
TypedReducer<String, ViewRecurringInvoice>(
(String selectedId, dynamic action) => action.recurringInvoiceId),
TypedReducer<String, AddRecurringInvoiceSuccess>(
(String selectedId, dynamic action) => action.recurringInvoice.id),
TypedReducer<String, SelectCompany>(
(selectedId, action) => action.clearSelection ? '' : selectedId),
TypedReducer<String, DeleteRecurringInvoicesSuccess>(
(selectedId, action) => ''),
TypedReducer<String, ArchiveRecurringInvoicesSuccess>(
(selectedId, action) => ''),
TypedReducer<String, ClearEntityFilter>((selectedId, action) => ''),
TypedReducer<String, FilterByEntity>((selectedId, action) =>
action.clearSelection
? ''
: action.entityType == EntityType.recurringInvoice
? action.entityId
: selectedId),
]);
final editingReducer = combineReducers<InvoiceEntity>([
TypedReducer<InvoiceEntity, SaveRecurringInvoiceSuccess>(_updateEditing),
TypedReducer<InvoiceEntity, AddRecurringInvoiceSuccess>(_updateEditing),
TypedReducer<InvoiceEntity, RestoreRecurringInvoicesSuccess>(
(recurringInvoices, action) {
return action.recurringInvoices[0];
}),
TypedReducer<InvoiceEntity, ArchiveRecurringInvoicesSuccess>(
(recurringInvoices, action) {
return action.recurringInvoices[0];
}),
TypedReducer<InvoiceEntity, DeleteRecurringInvoicesSuccess>(
(recurringInvoices, action) {
return action.recurringInvoices[0];
}),
TypedReducer<InvoiceEntity, EditRecurringInvoice>(_updateEditing),
TypedReducer<InvoiceEntity, UpdateRecurringInvoice>(
(recurringInvoice, action) {
return action.recurringInvoice.rebuild((b) => b..isChanged = true);
}),
TypedReducer<InvoiceEntity, DiscardChanges>(_clearEditing),
]);
InvoiceEntity _clearEditing(InvoiceEntity recurringInvoice, dynamic action) {
return InvoiceEntity();
}
InvoiceEntity _updateEditing(InvoiceEntity recurringInvoice, dynamic action) {
return action.recurringInvoice;
}
final recurringInvoiceListReducer = combineReducers<ListUIState>([
TypedReducer<ListUIState, SortRecurringInvoices>(_sortRecurringInvoices),
TypedReducer<ListUIState, FilterRecurringInvoicesByState>(
_filterRecurringInvoicesByState),
TypedReducer<ListUIState, FilterRecurringInvoices>(_filterRecurringInvoices),
TypedReducer<ListUIState, FilterRecurringInvoicesByCustom1>(
_filterRecurringInvoicesByCustom1),
TypedReducer<ListUIState, FilterRecurringInvoicesByCustom2>(
_filterRecurringInvoicesByCustom2),
TypedReducer<ListUIState, StartRecurringInvoiceMultiselect>(
_startListMultiselect),
TypedReducer<ListUIState, AddToRecurringInvoiceMultiselect>(
_addToListMultiselect),
TypedReducer<ListUIState, RemoveFromRecurringInvoiceMultiselect>(
_removeFromListMultiselect),
TypedReducer<ListUIState, ClearRecurringInvoiceMultiselect>(
_clearListMultiselect),
]);
ListUIState _filterRecurringInvoicesByCustom1(
ListUIState recurringInvoiceListState,
FilterRecurringInvoicesByCustom1 action) {
if (recurringInvoiceListState.custom1Filters.contains(action.value)) {
return recurringInvoiceListState
.rebuild((b) => b..custom1Filters.remove(action.value));
} else {
return recurringInvoiceListState
.rebuild((b) => b..custom1Filters.add(action.value));
}
}
ListUIState _filterRecurringInvoicesByCustom2(
ListUIState recurringInvoiceListState,
FilterRecurringInvoicesByCustom2 action) {
if (recurringInvoiceListState.custom2Filters.contains(action.value)) {
return recurringInvoiceListState
.rebuild((b) => b..custom2Filters.remove(action.value));
} else {
return recurringInvoiceListState
.rebuild((b) => b..custom2Filters.add(action.value));
}
}
ListUIState _filterRecurringInvoicesByState(
ListUIState recurringInvoiceListState,
FilterRecurringInvoicesByState action) {
if (recurringInvoiceListState.stateFilters.contains(action.state)) {
return recurringInvoiceListState
.rebuild((b) => b..stateFilters.remove(action.state));
} else {
return recurringInvoiceListState
.rebuild((b) => b..stateFilters.add(action.state));
}
}
ListUIState _filterRecurringInvoices(
ListUIState recurringInvoiceListState, FilterRecurringInvoices action) {
return recurringInvoiceListState.rebuild((b) => b
..filter = action.filter
..filterClearedAt = action.filter == null
? DateTime.now().millisecondsSinceEpoch
: recurringInvoiceListState.filterClearedAt);
}
ListUIState _sortRecurringInvoices(
ListUIState recurringInvoiceListState, SortRecurringInvoices action) {
return recurringInvoiceListState.rebuild((b) => b
..sortAscending = b.sortField != action.field || !b.sortAscending
..sortField = action.field);
}
ListUIState _startListMultiselect(
ListUIState productListState, StartRecurringInvoiceMultiselect action) {
return productListState.rebuild((b) => b..selectedIds = ListBuilder());
}
ListUIState _addToListMultiselect(
ListUIState productListState, AddToRecurringInvoiceMultiselect action) {
return productListState.rebuild((b) => b..selectedIds.add(action.entity.id));
}
ListUIState _removeFromListMultiselect(ListUIState productListState,
RemoveFromRecurringInvoiceMultiselect action) {
return productListState
.rebuild((b) => b..selectedIds.remove(action.entity.id));
}
ListUIState _clearListMultiselect(
ListUIState productListState, ClearRecurringInvoiceMultiselect action) {
return productListState.rebuild((b) => b..selectedIds = null);
}
final recurringInvoicesReducer = combineReducers<RecurringInvoiceState>([
TypedReducer<RecurringInvoiceState, SaveRecurringInvoiceSuccess>(
_updateRecurringInvoice),
TypedReducer<RecurringInvoiceState, AddRecurringInvoiceSuccess>(
_addRecurringInvoice),
TypedReducer<RecurringInvoiceState, LoadRecurringInvoicesSuccess>(
_setLoadedRecurringInvoices),
TypedReducer<RecurringInvoiceState, LoadRecurringInvoiceSuccess>(
_setLoadedRecurringInvoice),
TypedReducer<RecurringInvoiceState, LoadCompanySuccess>(_setLoadedCompany),
TypedReducer<RecurringInvoiceState, ArchiveRecurringInvoicesSuccess>(
_archiveRecurringInvoiceSuccess),
TypedReducer<RecurringInvoiceState, DeleteRecurringInvoicesSuccess>(
_deleteRecurringInvoiceSuccess),
TypedReducer<RecurringInvoiceState, RestoreRecurringInvoicesSuccess>(
_restoreRecurringInvoiceSuccess),
]);
RecurringInvoiceState _archiveRecurringInvoiceSuccess(
RecurringInvoiceState recurringInvoiceState,
ArchiveRecurringInvoicesSuccess action) {
return recurringInvoiceState.rebuild((b) {
for (final recurringInvoice in action.recurringInvoices) {
b.map[recurringInvoice.id] = recurringInvoice;
}
});
}
RecurringInvoiceState _deleteRecurringInvoiceSuccess(
RecurringInvoiceState recurringInvoiceState,
DeleteRecurringInvoicesSuccess action) {
return recurringInvoiceState.rebuild((b) {
for (final recurringInvoice in action.recurringInvoices) {
b.map[recurringInvoice.id] = recurringInvoice;
}
});
}
RecurringInvoiceState _restoreRecurringInvoiceSuccess(
RecurringInvoiceState recurringInvoiceState,
RestoreRecurringInvoicesSuccess action) {
return recurringInvoiceState.rebuild((b) {
for (final recurringInvoice in action.recurringInvoices) {
b.map[recurringInvoice.id] = recurringInvoice;
}
});
}
RecurringInvoiceState _addRecurringInvoice(
RecurringInvoiceState recurringInvoiceState,
AddRecurringInvoiceSuccess action) {
return recurringInvoiceState.rebuild((b) => b
..map[action.recurringInvoice.id] = action.recurringInvoice
..list.add(action.recurringInvoice.id));
}
RecurringInvoiceState _updateRecurringInvoice(
RecurringInvoiceState recurringInvoiceState,
SaveRecurringInvoiceSuccess action) {
return recurringInvoiceState.rebuild(
(b) => b..map[action.recurringInvoice.id] = action.recurringInvoice);
}
RecurringInvoiceState _setLoadedRecurringInvoice(
RecurringInvoiceState recurringInvoiceState,
LoadRecurringInvoiceSuccess action) {
return recurringInvoiceState.rebuild(
(b) => b..map[action.recurringInvoice.id] = action.recurringInvoice);
}
RecurringInvoiceState _setLoadedRecurringInvoices(
RecurringInvoiceState recurringInvoiceState,
LoadRecurringInvoicesSuccess action) =>
recurringInvoiceState.loadRecurringInvoices(action.recurringInvoices);
RecurringInvoiceState _setLoadedCompany(
RecurringInvoiceState recurringInvoiceState, LoadCompanySuccess action) {
final company = action.userCompany.company;
return recurringInvoiceState.loadRecurringInvoices(company.recurringInvoices);
}

View File

@ -0,0 +1,83 @@
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
import 'package:invoiceninja_flutter/redux/static/static_state.dart';
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 memoizedFilteredRecurringInvoiceList = memo8((
String filterEntityId,
EntityType filterEntityType,
BuiltMap<String, InvoiceEntity> recurringInvoiceMap,
BuiltMap<String, ClientEntity> clientMap,
BuiltList<String> recurringInvoiceList,
ListUIState recurringInvoiceListState,
StaticState staticState,
BuiltMap<String, UserEntity> userMap,
) =>
filteredRecurringInvoicesSelector(
filterEntityId,
filterEntityType,
recurringInvoiceMap,
clientMap,
recurringInvoiceList,
recurringInvoiceListState,
staticState,
userMap,
));
List<String> filteredRecurringInvoicesSelector(
String filterEntityId,
EntityType filterEntityType,
BuiltMap<String, InvoiceEntity> recurringInvoiceMap,
BuiltMap<String, ClientEntity> clientMap,
BuiltList<String> recurringInvoiceList,
ListUIState recurringInvoiceListState,
StaticState staticState,
BuiltMap<String, UserEntity> userMap,
) {
final list = recurringInvoiceList.where((recurringInvoiceId) {
final recurringInvoice = recurringInvoiceMap[recurringInvoiceId];
if (filterEntityId != null && recurringInvoice.id != filterEntityId) {
return false;
} else {}
if (!recurringInvoice
.matchesStates(recurringInvoiceListState.stateFilters)) {
return false;
}
if (recurringInvoiceListState.custom1Filters.isNotEmpty &&
!recurringInvoiceListState.custom1Filters
.contains(recurringInvoice.customValue1)) {
return false;
}
if (recurringInvoiceListState.custom2Filters.isNotEmpty &&
!recurringInvoiceListState.custom2Filters
.contains(recurringInvoice.customValue2)) {
return false;
}
return recurringInvoice.matchesFilter(recurringInvoiceListState.filter);
}).toList();
list.sort((recurringInvoiceAId, recurringInvoiceBId) {
final recurringInvoiceA = recurringInvoiceMap[recurringInvoiceAId];
final recurringInvoiceB = recurringInvoiceMap[recurringInvoiceBId];
return recurringInvoiceA.compareTo(
invoice: recurringInvoiceB,
sortField: recurringInvoiceListState.sortField,
sortAscending: recurringInvoiceListState.sortAscending,
clientMap: clientMap,
staticState: staticState,
userMap: userMap,
);
});
return list;
}
bool hasRecurringInvoiceChanges(InvoiceEntity recurringInvoice,
BuiltMap<String, InvoiceEntity> recurringInvoiceMap) =>
recurringInvoice.isNew
? recurringInvoice.isChanged
: recurringInvoice != recurringInvoiceMap[recurringInvoice.id];

View File

@ -0,0 +1,76 @@
import 'dart:async';
import 'package:built_value/built_value.dart';
import 'package:built_value/serializer.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/data/models/invoice_model.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/data/models/models.dart';
part 'recurring_invoice_state.g.dart';
abstract class RecurringInvoiceState
implements Built<RecurringInvoiceState, RecurringInvoiceStateBuilder> {
factory RecurringInvoiceState() {
return _$RecurringInvoiceState._(
map: BuiltMap<String, InvoiceEntity>(),
list: BuiltList<String>(),
);
}
RecurringInvoiceState._();
@override
@memoized
int get hashCode;
BuiltMap<String, InvoiceEntity> get map;
BuiltList<String> get list;
RecurringInvoiceState loadRecurringInvoices(
BuiltList<InvoiceEntity> clients) {
final map = Map<String, InvoiceEntity>.fromIterable(
clients,
key: (dynamic item) => item.id,
value: (dynamic item) => item,
);
return rebuild((b) => b
..map.addAll(map)
..list.replace((map.keys.toList() + list.toList()).toSet().toList()));
}
static Serializer<RecurringInvoiceState> get serializer =>
_$recurringInvoiceStateSerializer;
}
abstract class RecurringInvoiceUIState extends Object
with EntityUIState
implements Built<RecurringInvoiceUIState, RecurringInvoiceUIStateBuilder> {
factory RecurringInvoiceUIState() {
return _$RecurringInvoiceUIState._(
listUIState: ListUIState(InvoiceFields.invoiceNumber),
editing: InvoiceEntity(),
selectedId: '',
);
}
RecurringInvoiceUIState._();
@override
@memoized
int get hashCode;
@nullable
InvoiceEntity get editing;
@override
bool get isCreatingNew => editing.isNew;
@override
String get editingId => editing.id;
static Serializer<RecurringInvoiceUIState> get serializer =>
_$recurringInvoiceUIStateSerializer;
}

View File

@ -0,0 +1,409 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'recurring_invoice_state.dart';
// **************************************************************************
// BuiltValueGenerator
// **************************************************************************
Serializer<RecurringInvoiceState> _$recurringInvoiceStateSerializer =
new _$RecurringInvoiceStateSerializer();
Serializer<RecurringInvoiceUIState> _$recurringInvoiceUIStateSerializer =
new _$RecurringInvoiceUIStateSerializer();
class _$RecurringInvoiceStateSerializer
implements StructuredSerializer<RecurringInvoiceState> {
@override
final Iterable<Type> types = const [
RecurringInvoiceState,
_$RecurringInvoiceState
];
@override
final String wireName = 'RecurringInvoiceState';
@override
Iterable<Object> serialize(
Serializers serializers, RecurringInvoiceState object,
{FullType specifiedType = FullType.unspecified}) {
final result = <Object>[
'map',
serializers.serialize(object.map,
specifiedType: const FullType(BuiltMap,
const [const FullType(String), const FullType(InvoiceEntity)])),
'list',
serializers.serialize(object.list,
specifiedType:
const FullType(BuiltList, const [const FullType(String)])),
];
return result;
}
@override
RecurringInvoiceState deserialize(
Serializers serializers, Iterable<Object> serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new RecurringInvoiceStateBuilder();
final iterator = serialized.iterator;
while (iterator.moveNext()) {
final key = iterator.current as String;
iterator.moveNext();
final dynamic value = iterator.current;
switch (key) {
case 'map':
result.map.replace(serializers.deserialize(value,
specifiedType: const FullType(BuiltMap, const [
const FullType(String),
const FullType(InvoiceEntity)
])));
break;
case 'list':
result.list.replace(serializers.deserialize(value,
specifiedType:
const FullType(BuiltList, const [const FullType(String)]))
as BuiltList<Object>);
break;
}
}
return result.build();
}
}
class _$RecurringInvoiceUIStateSerializer
implements StructuredSerializer<RecurringInvoiceUIState> {
@override
final Iterable<Type> types = const [
RecurringInvoiceUIState,
_$RecurringInvoiceUIState
];
@override
final String wireName = 'RecurringInvoiceUIState';
@override
Iterable<Object> serialize(
Serializers serializers, RecurringInvoiceUIState object,
{FullType specifiedType = FullType.unspecified}) {
final result = <Object>[
'listUIState',
serializers.serialize(object.listUIState,
specifiedType: const FullType(ListUIState)),
];
if (object.editing != null) {
result
..add('editing')
..add(serializers.serialize(object.editing,
specifiedType: const FullType(InvoiceEntity)));
}
if (object.selectedId != null) {
result
..add('selectedId')
..add(serializers.serialize(object.selectedId,
specifiedType: const FullType(String)));
}
return result;
}
@override
RecurringInvoiceUIState deserialize(
Serializers serializers, Iterable<Object> serialized,
{FullType specifiedType = FullType.unspecified}) {
final result = new RecurringInvoiceUIStateBuilder();
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(InvoiceEntity)) as InvoiceEntity);
break;
case 'listUIState':
result.listUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(ListUIState)) as ListUIState);
break;
case 'selectedId':
result.selectedId = serializers.deserialize(value,
specifiedType: const FullType(String)) as String;
break;
}
}
return result.build();
}
}
class _$RecurringInvoiceState extends RecurringInvoiceState {
@override
final BuiltMap<String, InvoiceEntity> map;
@override
final BuiltList<String> list;
factory _$RecurringInvoiceState(
[void Function(RecurringInvoiceStateBuilder) updates]) =>
(new RecurringInvoiceStateBuilder()..update(updates)).build();
_$RecurringInvoiceState._({this.map, this.list}) : super._() {
if (map == null) {
throw new BuiltValueNullFieldError('RecurringInvoiceState', 'map');
}
if (list == null) {
throw new BuiltValueNullFieldError('RecurringInvoiceState', 'list');
}
}
@override
RecurringInvoiceState rebuild(
void Function(RecurringInvoiceStateBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
RecurringInvoiceStateBuilder toBuilder() =>
new RecurringInvoiceStateBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is RecurringInvoiceState &&
map == other.map &&
list == other.list;
}
int __hashCode;
@override
int get hashCode {
return __hashCode ??= $jf($jc($jc(0, map.hashCode), list.hashCode));
}
@override
String toString() {
return (newBuiltValueToStringHelper('RecurringInvoiceState')
..add('map', map)
..add('list', list))
.toString();
}
}
class RecurringInvoiceStateBuilder
implements Builder<RecurringInvoiceState, RecurringInvoiceStateBuilder> {
_$RecurringInvoiceState _$v;
MapBuilder<String, InvoiceEntity> _map;
MapBuilder<String, InvoiceEntity> get map =>
_$this._map ??= new MapBuilder<String, InvoiceEntity>();
set map(MapBuilder<String, InvoiceEntity> map) => _$this._map = map;
ListBuilder<String> _list;
ListBuilder<String> get list => _$this._list ??= new ListBuilder<String>();
set list(ListBuilder<String> list) => _$this._list = list;
RecurringInvoiceStateBuilder();
RecurringInvoiceStateBuilder get _$this {
if (_$v != null) {
_map = _$v.map?.toBuilder();
_list = _$v.list?.toBuilder();
_$v = null;
}
return this;
}
@override
void replace(RecurringInvoiceState other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$RecurringInvoiceState;
}
@override
void update(void Function(RecurringInvoiceStateBuilder) updates) {
if (updates != null) updates(this);
}
@override
_$RecurringInvoiceState build() {
_$RecurringInvoiceState _$result;
try {
_$result = _$v ??
new _$RecurringInvoiceState._(map: map.build(), list: list.build());
} catch (_) {
String _$failedField;
try {
_$failedField = 'map';
map.build();
_$failedField = 'list';
list.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
'RecurringInvoiceState', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
}
class _$RecurringInvoiceUIState extends RecurringInvoiceUIState {
@override
final InvoiceEntity editing;
@override
final ListUIState listUIState;
@override
final String selectedId;
@override
final Completer<SelectableEntity> saveCompleter;
@override
final Completer<Null> cancelCompleter;
factory _$RecurringInvoiceUIState(
[void Function(RecurringInvoiceUIStateBuilder) updates]) =>
(new RecurringInvoiceUIStateBuilder()..update(updates)).build();
_$RecurringInvoiceUIState._(
{this.editing,
this.listUIState,
this.selectedId,
this.saveCompleter,
this.cancelCompleter})
: super._() {
if (listUIState == null) {
throw new BuiltValueNullFieldError(
'RecurringInvoiceUIState', 'listUIState');
}
}
@override
RecurringInvoiceUIState rebuild(
void Function(RecurringInvoiceUIStateBuilder) updates) =>
(toBuilder()..update(updates)).build();
@override
RecurringInvoiceUIStateBuilder toBuilder() =>
new RecurringInvoiceUIStateBuilder()..replace(this);
@override
bool operator ==(Object other) {
if (identical(other, this)) return true;
return other is RecurringInvoiceUIState &&
editing == other.editing &&
listUIState == other.listUIState &&
selectedId == other.selectedId &&
saveCompleter == other.saveCompleter &&
cancelCompleter == other.cancelCompleter;
}
int __hashCode;
@override
int get hashCode {
return __hashCode ??= $jf($jc(
$jc(
$jc($jc($jc(0, editing.hashCode), listUIState.hashCode),
selectedId.hashCode),
saveCompleter.hashCode),
cancelCompleter.hashCode));
}
@override
String toString() {
return (newBuiltValueToStringHelper('RecurringInvoiceUIState')
..add('editing', editing)
..add('listUIState', listUIState)
..add('selectedId', selectedId)
..add('saveCompleter', saveCompleter)
..add('cancelCompleter', cancelCompleter))
.toString();
}
}
class RecurringInvoiceUIStateBuilder
implements
Builder<RecurringInvoiceUIState, RecurringInvoiceUIStateBuilder> {
_$RecurringInvoiceUIState _$v;
InvoiceEntityBuilder _editing;
InvoiceEntityBuilder get editing =>
_$this._editing ??= new InvoiceEntityBuilder();
set editing(InvoiceEntityBuilder editing) => _$this._editing = editing;
ListUIStateBuilder _listUIState;
ListUIStateBuilder get listUIState =>
_$this._listUIState ??= new ListUIStateBuilder();
set listUIState(ListUIStateBuilder listUIState) =>
_$this._listUIState = listUIState;
String _selectedId;
String get selectedId => _$this._selectedId;
set selectedId(String selectedId) => _$this._selectedId = selectedId;
Completer<SelectableEntity> _saveCompleter;
Completer<SelectableEntity> get saveCompleter => _$this._saveCompleter;
set saveCompleter(Completer<SelectableEntity> saveCompleter) =>
_$this._saveCompleter = saveCompleter;
Completer<Null> _cancelCompleter;
Completer<Null> get cancelCompleter => _$this._cancelCompleter;
set cancelCompleter(Completer<Null> cancelCompleter) =>
_$this._cancelCompleter = cancelCompleter;
RecurringInvoiceUIStateBuilder();
RecurringInvoiceUIStateBuilder get _$this {
if (_$v != null) {
_editing = _$v.editing?.toBuilder();
_listUIState = _$v.listUIState?.toBuilder();
_selectedId = _$v.selectedId;
_saveCompleter = _$v.saveCompleter;
_cancelCompleter = _$v.cancelCompleter;
_$v = null;
}
return this;
}
@override
void replace(RecurringInvoiceUIState other) {
if (other == null) {
throw new ArgumentError.notNull('other');
}
_$v = other as _$RecurringInvoiceUIState;
}
@override
void update(void Function(RecurringInvoiceUIStateBuilder) updates) {
if (updates != null) updates(this);
}
@override
_$RecurringInvoiceUIState build() {
_$RecurringInvoiceUIState _$result;
try {
_$result = _$v ??
new _$RecurringInvoiceUIState._(
editing: _editing?.build(),
listUIState: listUIState.build(),
selectedId: selectedId,
saveCompleter: saveCompleter,
cancelCompleter: cancelCompleter);
} catch (_) {
String _$failedField;
try {
_$failedField = 'editing';
_editing?.build();
_$failedField = 'listUIState';
listUIState.build();
} catch (e) {
throw new BuiltValueNestedFieldError(
'RecurringInvoiceUIState', _$failedField, e.toString());
}
rethrow;
}
replace(_$result);
return _$result;
}
}
// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new

View File

@ -23,6 +23,8 @@ import 'package:invoiceninja_flutter/redux/user/user_actions.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/webhook/webhook_actions.dart';
import 'package:invoiceninja_flutter/redux/token/token_actions.dart';
@ -322,6 +324,19 @@ Reducer<BuiltList<HistoryRecord>> historyReducer = combineReducers([
_addToHistory(historyList,
HistoryRecord(id: action.group.id, entityType: EntityType.group))),
// STARTER: history - do not remove comment
TypedReducer<BuiltList<HistoryRecord>, ViewRecurringInvoice>(
(historyList, action) => _addToHistory(
historyList,
HistoryRecord(
id: action.recurringInvoiceId,
entityType: EntityType.recurringInvoice))),
TypedReducer<BuiltList<HistoryRecord>, EditRecurringInvoice>(
(historyList, action) => _addToHistory(
historyList,
HistoryRecord(
id: action.recurringInvoice.id,
entityType: EntityType.recurringInvoice))),
TypedReducer<BuiltList<HistoryRecord>, ViewWebhook>((historyList, action) =>
_addToHistory(historyList,
HistoryRecord(id: action.webhookId, entityType: EntityType.webhook))),

View File

@ -33,6 +33,8 @@ import 'package:invoiceninja_flutter/redux/task/task_reducer.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_reducer.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_reducer.dart';
import 'package:invoiceninja_flutter/redux/webhook/webhook_reducer.dart';
import 'package:invoiceninja_flutter/redux/token/token_reducer.dart';
import 'package:invoiceninja_flutter/redux/payment_term/payment_term_reducer.dart';
@ -78,6 +80,8 @@ UIState uiReducer(UIState state, dynamic action) {
.replace(dashboardUIReducer(state.dashboardUIState, action))
..reportsUIState.replace(reportsUIReducer(state.reportsUIState, action))
// STARTER: reducer - do not remove comment
..recurringInvoiceUIState.replace(
recurringInvoiceUIReducer(state.recurringInvoiceUIState, action))
..webhookUIState.replace(webhookUIReducer(state.webhookUIState, action))
..tokenUIState.replace(tokenUIReducer(state.tokenUIState, action))
..paymentTermUIState

View File

@ -16,6 +16,8 @@ import 'package:invoiceninja_flutter/redux/quote/quote_state.dart';
import 'package:invoiceninja_flutter/redux/task/task_state.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_state.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_state.dart';
import 'package:invoiceninja_flutter/redux/webhook/webhook_state.dart';
import 'package:invoiceninja_flutter/redux/token/token_state.dart';
import 'package:invoiceninja_flutter/redux/payment_term/payment_term_state.dart';
@ -40,6 +42,8 @@ abstract class UIState implements Built<UIState, UIStateBuilder> {
clientUIState: ClientUIState(),
invoiceUIState: InvoiceUIState(),
// STARTER: constructor - do not remove comment
recurringInvoiceUIState: RecurringInvoiceUIState(),
webhookUIState: WebhookUIState(),
tokenUIState: TokenUIState(),
paymentTermUIState: PaymentTermUIState(),
@ -93,6 +97,8 @@ abstract class UIState implements Built<UIState, UIStateBuilder> {
InvoiceUIState get invoiceUIState;
// STARTER: properties - do not remove comment
RecurringInvoiceUIState get recurringInvoiceUIState;
WebhookUIState get webhookUIState;
TokenUIState get tokenUIState;

View File

@ -42,6 +42,9 @@ class _$UIStateSerializer implements StructuredSerializer<UIState> {
'invoiceUIState',
serializers.serialize(object.invoiceUIState,
specifiedType: const FullType(InvoiceUIState)),
'recurringInvoiceUIState',
serializers.serialize(object.recurringInvoiceUIState,
specifiedType: const FullType(RecurringInvoiceUIState)),
'webhookUIState',
serializers.serialize(object.webhookUIState,
specifiedType: const FullType(WebhookUIState)),
@ -174,6 +177,11 @@ class _$UIStateSerializer implements StructuredSerializer<UIState> {
result.invoiceUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(InvoiceUIState)) as InvoiceUIState);
break;
case 'recurringInvoiceUIState':
result.recurringInvoiceUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(RecurringInvoiceUIState))
as RecurringInvoiceUIState);
break;
case 'webhookUIState':
result.webhookUIState.replace(serializers.deserialize(value,
specifiedType: const FullType(WebhookUIState)) as WebhookUIState);
@ -281,6 +289,8 @@ class _$UIState extends UIState {
@override
final InvoiceUIState invoiceUIState;
@override
final RecurringInvoiceUIState recurringInvoiceUIState;
@override
final WebhookUIState webhookUIState;
@override
final TokenUIState tokenUIState;
@ -332,6 +342,7 @@ class _$UIState extends UIState {
this.productUIState,
this.clientUIState,
this.invoiceUIState,
this.recurringInvoiceUIState,
this.webhookUIState,
this.tokenUIState,
this.paymentTermUIState,
@ -375,6 +386,9 @@ class _$UIState extends UIState {
if (invoiceUIState == null) {
throw new BuiltValueNullFieldError('UIState', 'invoiceUIState');
}
if (recurringInvoiceUIState == null) {
throw new BuiltValueNullFieldError('UIState', 'recurringInvoiceUIState');
}
if (webhookUIState == null) {
throw new BuiltValueNullFieldError('UIState', 'webhookUIState');
}
@ -453,6 +467,7 @@ class _$UIState extends UIState {
productUIState == other.productUIState &&
clientUIState == other.clientUIState &&
invoiceUIState == other.invoiceUIState &&
recurringInvoiceUIState == other.recurringInvoiceUIState &&
webhookUIState == other.webhookUIState &&
tokenUIState == other.tokenUIState &&
paymentTermUIState == other.paymentTermUIState &&
@ -494,8 +509,8 @@ class _$UIState extends UIState {
$jc(
$jc(
$jc(
$jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc(0, selectedCompanyIndex.hashCode), currentRoute.hashCode), previousRoute.hashCode), filterEntityId.hashCode), filterEntityType.hashCode), filter.hashCode), filterClearedAt.hashCode), dashboardUIState.hashCode), productUIState.hashCode), clientUIState.hashCode),
invoiceUIState.hashCode),
$jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc($jc(0, selectedCompanyIndex.hashCode), currentRoute.hashCode), previousRoute.hashCode), filterEntityId.hashCode), filterEntityType.hashCode), filter.hashCode), filterClearedAt.hashCode), dashboardUIState.hashCode), productUIState.hashCode), clientUIState.hashCode), invoiceUIState.hashCode),
recurringInvoiceUIState.hashCode),
webhookUIState.hashCode),
tokenUIState.hashCode),
paymentTermUIState.hashCode),
@ -530,6 +545,7 @@ class _$UIState extends UIState {
..add('productUIState', productUIState)
..add('clientUIState', clientUIState)
..add('invoiceUIState', invoiceUIState)
..add('recurringInvoiceUIState', recurringInvoiceUIState)
..add('webhookUIState', webhookUIState)
..add('tokenUIState', tokenUIState)
..add('paymentTermUIState', paymentTermUIState)
@ -612,6 +628,13 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
set invoiceUIState(InvoiceUIStateBuilder invoiceUIState) =>
_$this._invoiceUIState = invoiceUIState;
RecurringInvoiceUIStateBuilder _recurringInvoiceUIState;
RecurringInvoiceUIStateBuilder get recurringInvoiceUIState =>
_$this._recurringInvoiceUIState ??= new RecurringInvoiceUIStateBuilder();
set recurringInvoiceUIState(
RecurringInvoiceUIStateBuilder recurringInvoiceUIState) =>
_$this._recurringInvoiceUIState = recurringInvoiceUIState;
WebhookUIStateBuilder _webhookUIState;
WebhookUIStateBuilder get webhookUIState =>
_$this._webhookUIState ??= new WebhookUIStateBuilder();
@ -736,6 +759,7 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
_productUIState = _$v.productUIState?.toBuilder();
_clientUIState = _$v.clientUIState?.toBuilder();
_invoiceUIState = _$v.invoiceUIState?.toBuilder();
_recurringInvoiceUIState = _$v.recurringInvoiceUIState?.toBuilder();
_webhookUIState = _$v.webhookUIState?.toBuilder();
_tokenUIState = _$v.tokenUIState?.toBuilder();
_paymentTermUIState = _$v.paymentTermUIState?.toBuilder();
@ -789,6 +813,7 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
productUIState: productUIState.build(),
clientUIState: clientUIState.build(),
invoiceUIState: invoiceUIState.build(),
recurringInvoiceUIState: recurringInvoiceUIState.build(),
webhookUIState: webhookUIState.build(),
tokenUIState: tokenUIState.build(),
paymentTermUIState: paymentTermUIState.build(),
@ -818,6 +843,8 @@ class UIStateBuilder implements Builder<UIState, UIStateBuilder> {
clientUIState.build();
_$failedField = 'invoiceUIState';
invoiceUIState.build();
_$failedField = 'recurringInvoiceUIState';
recurringInvoiceUIState.build();
_$failedField = 'webhookUIState';
webhookUIState.build();
_$failedField = 'tokenUIState';

View File

@ -21,8 +21,7 @@ class AppButton extends StatelessWidget {
final button = RaisedButton(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
color: color ?? Theme.of(context).buttonColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(5)),
child: iconData != null
? IconText(
icon: iconData,

View File

@ -235,6 +235,13 @@ class MenuDrawer extends StatelessWidget {
title: localization.invoices,
iconTooltip: localization.newInvoice,
),
DrawerTile(
company: company,
entityType: EntityType.recurringInvoice,
icon: getEntityIcon(EntityType.recurringInvoice),
title: localization.recurringInvoices,
iconTooltip: localization.newRecurringInvoice,
),
DrawerTile(
company: company,
entityType: EntityType.payment,

View File

@ -54,68 +54,51 @@ class CompanyGatewayListItem extends StatelessWidget {
trailing: onRemovePressed == null
? null
: FlatButton(
child: Text(AppLocalization
.of(context)
.remove),
onPressed: onRemovePressed,
),
child: Text(AppLocalization.of(context).remove),
onPressed: onRemovePressed,
),
leading: showCheckbox
? IgnorePointer(
ignoring: listUIState.isInMultiselect(),
child: Checkbox(
value: isChecked,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (value) => onCheckboxChanged(value),
activeColor: Theme
.of(context)
.accentColor,
),
)
ignoring: listUIState.isInMultiselect(),
child: Checkbox(
value: isChecked,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (value) => onCheckboxChanged(value),
activeColor: Theme.of(context).accentColor,
),
)
: Icon(Icons.drag_handle),
title: Container(
width: MediaQuery
.of(context)
.size
.width,
width: MediaQuery.of(context).size.width,
child: Row(
children: <Widget>[
Expanded(
child: Text(
companyGateway.label,
style: Theme
.of(context)
.textTheme
.headline6,
children: <Widget>[
Expanded(
child: Text(
companyGateway.label,
style: Theme.of(context).textTheme.headline6,
),
),
Text(formatNumber(companyGateway.listDisplayAmount, context),
style: Theme.of(context).textTheme.headline6),
],
),
),
subtitle: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (companyGateway.isTestMode) Text(localization.testMode),
subtitle != null && subtitle.isNotEmpty
? Text(
subtitle,
maxLines: 3,
overflow: TextOverflow.ellipsis,
)
: Container(),
EntityStateLabel(companyGateway),
],
),
),
Text(formatNumber(companyGateway.listDisplayAmount, context),
style: Theme
.of(context)
.textTheme
.headline6),
],
),
),
subtitle: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (companyGateway.isTestMode)
Text(localization.testMode),
subtitle != null && subtitle.isNotEmpty
? Text(
subtitle,
maxLines: 3,
overflow: TextOverflow.ellipsis,
)
: Container(),
EntityStateLabel(companyGateway),
]
,
)
,
)
,
);
}
}

View File

@ -0,0 +1,109 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/edit_scaffold.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/edit/recurring_invoice_edit_vm.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
class RecurringInvoiceEdit extends StatefulWidget {
const RecurringInvoiceEdit({
Key key,
@required this.viewModel,
}) : super(key: key);
final RecurringInvoiceEditVM viewModel;
@override
_RecurringInvoiceEditState createState() => _RecurringInvoiceEditState();
}
class _RecurringInvoiceEditState extends State<RecurringInvoiceEdit> {
static final GlobalKey<FormState> _formKey =
GlobalKey<FormState>(debugLabel: '_recurringInvoiceEdit');
final _debouncer = Debouncer();
// 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 recurringInvoice = widget.viewModel.recurringInvoice;
// 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() {
_debouncer.run(() {
final recurringInvoice =
widget.viewModel.recurringInvoice.rebuild((b) => b
// STARTER: set value - do not remove comment
);
if (recurringInvoice != widget.viewModel.recurringInvoice) {
widget.viewModel.onChanged(recurringInvoice);
}
});
}
@override
Widget build(BuildContext context) {
final viewModel = widget.viewModel;
final localization = AppLocalization.of(context);
final recurringInvoice = viewModel.recurringInvoice;
return EditScaffold(
title: recurringInvoice.isNew
? localization.newRecurringInvoice
: localization.editRecurringInvoice,
onCancelPressed: (context) => viewModel.onCancelPressed(context),
onSavePressed: (context) {
final bool isValid = _formKey.currentState.validate();
/*
setState(() {
_autoValidate = !isValid;
});
*/
if (!isValid) {
return;
}
viewModel.onSavePressed(context);
},
body: Form(
key: _formKey,
child: Builder(builder: (BuildContext context) {
return ListView(
children: <Widget>[
FormCard(
children: <Widget>[
// STARTER: widgets - do not remove comment
],
),
],
);
})),
);
}
}

View File

@ -0,0 +1,108 @@
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/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/view/recurring_invoice_view_vm.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/edit/recurring_invoice_edit.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class RecurringInvoiceEditScreen extends StatelessWidget {
const RecurringInvoiceEditScreen({Key key}) : super(key: key);
static const String route = '/recurring_invoice/edit';
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, RecurringInvoiceEditVM>(
converter: (Store<AppState> store) {
return RecurringInvoiceEditVM.fromStore(store);
},
builder: (context, viewModel) {
return RecurringInvoiceEdit(
viewModel: viewModel,
key: ValueKey(viewModel.recurringInvoice.id),
);
},
);
}
}
class RecurringInvoiceEditVM {
RecurringInvoiceEditVM({
@required this.state,
@required this.recurringInvoice,
@required this.company,
@required this.onChanged,
@required this.isSaving,
@required this.origRecurringInvoice,
@required this.onSavePressed,
@required this.onCancelPressed,
@required this.isLoading,
});
factory RecurringInvoiceEditVM.fromStore(Store<AppState> store) {
final state = store.state;
final recurringInvoice = state.recurringInvoiceUIState.editing;
return RecurringInvoiceEditVM(
state: state,
isLoading: state.isLoading,
isSaving: state.isSaving,
origRecurringInvoice:
state.recurringInvoiceState.map[recurringInvoice.id],
recurringInvoice: recurringInvoice,
company: state.company,
onChanged: (InvoiceEntity recurringInvoice) {
store.dispatch(UpdateRecurringInvoice(recurringInvoice));
},
onCancelPressed: (BuildContext context) {
createEntity(
context: context, entity: InvoiceEntity(), force: true);
},
onSavePressed: (BuildContext context) {
final Completer<InvoiceEntity> completer =
new Completer<InvoiceEntity>();
store.dispatch(SaveRecurringInvoiceRequest(
completer: completer, recurringInvoice: recurringInvoice));
return completer.future.then((savedRecurringInvoice) {
if (isMobile(context)) {
store
.dispatch(UpdateCurrentRoute(RecurringInvoiceViewScreen.route));
if (recurringInvoice.isNew) {
Navigator.of(context)
.pushReplacementNamed(RecurringInvoiceViewScreen.route);
} else {
Navigator.of(context).pop(savedRecurringInvoice);
}
} else {
viewEntity(
context: context, entity: savedRecurringInvoice, force: true);
}
}).catchError((Object error) {
showDialog<ErrorDialog>(
context: context,
builder: (BuildContext context) {
return ErrorDialog(error);
});
});
},
);
}
final InvoiceEntity recurringInvoice;
final CompanyEntity company;
final Function(InvoiceEntity) onChanged;
final Function(BuildContext) onSavePressed;
final Function(BuildContext) onCancelPressed;
final bool isLoading;
final bool isSaving;
final InvoiceEntity origRecurringInvoice;
final AppState state;
}

View File

@ -0,0 +1,103 @@
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
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 RecurringInvoiceListItem extends StatelessWidget {
const RecurringInvoiceListItem({
@required this.user,
@required this.recurringInvoice,
@required this.filter,
this.onTap,
this.onLongPress,
this.onCheckboxChanged,
this.isChecked = false,
});
final UserEntity user;
final GestureTapCallback onTap;
final GestureTapCallback onLongPress;
final InvoiceEntity recurringInvoice;
final String filter;
final Function(bool) onCheckboxChanged;
final bool isChecked;
@override
Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final uiState = state.uiState;
final recurringInvoiceUIState = uiState.recurringInvoiceUIState;
final listUIState = recurringInvoiceUIState.listUIState;
final isInMultiselect = listUIState.isInMultiselect();
final showCheckbox = onCheckboxChanged != null || isInMultiselect;
final filterMatch = filter != null && filter.isNotEmpty
? recurringInvoice.matchesFilterValue(filter)
: null;
final subtitle = filterMatch;
return DismissibleEntity(
userCompany: state.userCompany,
entity: recurringInvoice,
isSelected: recurringInvoice.id ==
(uiState.isEditing
? recurringInvoiceUIState.editing.id
: recurringInvoiceUIState.selectedId),
child: ListTile(
onTap: () => onTap != null
? onTap()
: selectEntity(entity: recurringInvoice, context: context),
onLongPress: () => onLongPress != null
? onLongPress()
: selectEntity(
entity: recurringInvoice, context: context, longPress: true),
leading: showCheckbox
? IgnorePointer(
ignoring: listUIState.isInMultiselect(),
child: Checkbox(
value: isChecked,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (value) => onCheckboxChanged(value),
activeColor: Theme.of(context).accentColor,
),
)
: null,
title: Container(
width: MediaQuery.of(context).size.width,
child: Row(
children: <Widget>[
Expanded(
child: Text(
recurringInvoice.number,
style: Theme.of(context).textTheme.headline6,
),
),
Text(formatNumber(recurringInvoice.listDisplayAmount, context),
style: Theme.of(context).textTheme.headline6),
],
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
subtitle != null && subtitle.isNotEmpty
? Text(
subtitle,
maxLines: 3,
overflow: TextOverflow.ellipsis,
)
: Container(),
EntityStateLabel(recurringInvoice),
],
),
),
);
}
}

View File

@ -0,0 +1,129 @@
import 'dart:async';
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
import 'package:invoiceninja_flutter/ui/app/tables/entity_list.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/recurring_invoice_list_item.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/recurring_invoice_presenter.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_selectors.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
class RecurringInvoiceListBuilder extends StatelessWidget {
const RecurringInvoiceListBuilder({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, RecurringInvoiceListVM>(
converter: RecurringInvoiceListVM.fromStore,
builder: (context, viewModel) {
return EntityList(
entityType: EntityType.recurringInvoice,
presenter: RecurringInvoicePresenter(),
state: viewModel.state,
entityList: viewModel.recurringInvoiceList,
tableColumns: viewModel.tableColumns,
onRefreshed: viewModel.onRefreshed,
onSortColumn: viewModel.onSortColumn,
onClearMultiselect: viewModel.onClearMultielsect,
itemBuilder: (BuildContext context, index) {
final state = viewModel.state;
final recurringInvoiceId = viewModel.recurringInvoiceList[index];
final recurringInvoice =
viewModel.recurringInvoiceMap[recurringInvoiceId];
final listState = state.getListState(EntityType.recurringInvoice);
final isInMultiselect = listState.isInMultiselect();
return RecurringInvoiceListItem(
user: viewModel.state.user,
filter: viewModel.filter,
recurringInvoice: recurringInvoice,
isChecked: isInMultiselect &&
listState.isSelected(recurringInvoice.id),
);
});
},
);
}
}
class RecurringInvoiceListVM {
RecurringInvoiceListVM({
@required this.state,
@required this.userCompany,
@required this.recurringInvoiceList,
@required this.recurringInvoiceMap,
@required this.filter,
@required this.isLoading,
@required this.listState,
@required this.onRefreshed,
@required this.onEntityAction,
@required this.tableColumns,
@required this.onSortColumn,
@required this.onClearMultielsect,
});
static RecurringInvoiceListVM fromStore(Store<AppState> store) {
Future<Null> _handleRefresh(BuildContext context) {
if (store.state.isLoading) {
return Future<Null>(null);
}
final completer = snackBarCompleter<Null>(
context, AppLocalization.of(context).refreshComplete);
store.dispatch(RefreshData(completer: completer));
return completer.future;
}
final state = store.state;
return RecurringInvoiceListVM(
state: state,
userCompany: state.userCompany,
listState: state.recurringInvoiceListState,
recurringInvoiceList: memoizedFilteredRecurringInvoiceList(
state.uiState.filterEntityId,
state.uiState.filterEntityType,
state.recurringInvoiceState.map,
state.clientState.map,
state.recurringInvoiceState.list,
state.recurringInvoiceListState,
state.staticState,
state.userState.map),
recurringInvoiceMap: state.recurringInvoiceState.map,
isLoading: state.isLoading,
filter: state.recurringInvoiceUIState.listUIState.filter,
onEntityAction: (BuildContext context, List<BaseEntity> recurringInvoices,
EntityAction action) =>
handleRecurringInvoiceAction(context, recurringInvoices, action),
onRefreshed: (context) => _handleRefresh(context),
tableColumns: state.userCompany.settings
.getTableColumns(EntityType.recurringInvoice) ??
RecurringInvoicePresenter.getDefaultTableFields(state.userCompany),
onSortColumn: (field) => store.dispatch(SortRecurringInvoices(field)),
onClearMultielsect: () =>
store.dispatch(ClearRecurringInvoiceMultiselect()),
);
}
final AppState state;
final UserCompanyEntity userCompany;
final List<String> recurringInvoiceList;
final BuiltMap<String, InvoiceEntity> recurringInvoiceMap;
final ListUIState listState;
final String filter;
final bool isLoading;
final Function(BuildContext) onRefreshed;
final Function(BuildContext, List<BaseEntity>, EntityAction) onEntityAction;
final List<String> tableColumns;
final Function(String) onSortColumn;
final Function onClearMultielsect;
}

View File

@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
class RecurringInvoicePresenter extends EntityPresenter {
static List<String> getDefaultTableFields(UserCompanyEntity userCompany) {
return [];
}
static List<String> getAllTableFields(UserCompanyEntity userCompany) {
return [
...getDefaultTableFields(userCompany),
...EntityPresenter.getBaseFields(),
];
}
@override
Widget getField({String field, BuildContext context}) {
//final state = StoreProvider.of<AppState>(context).state;
//final recurringInvoice = entity as InvoiceEntity;
switch (field) {
}
return super.getField(field: field, context: context);
}
}

View File

@ -0,0 +1,107 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/ui/app/app_bottom_bar.dart';
import 'package:invoiceninja_flutter/ui/app/list_scaffold.dart';
import 'package:invoiceninja_flutter/ui/app/list_filter.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/recurring_invoice_list_vm.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/recurring_invoice_presenter.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'recurring_invoice_screen_vm.dart';
class RecurringInvoiceScreen extends StatelessWidget {
const RecurringInvoiceScreen({
Key key,
@required this.viewModel,
}) : super(key: key);
static const String route = '/recurring_invoice';
final RecurringInvoiceScreenVM viewModel;
@override
Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final company = state.company;
final userCompany = state.userCompany;
final localization = AppLocalization.of(context);
return ListScaffold(
entityType: EntityType.recurringInvoice,
onHamburgerLongPress: () =>
store.dispatch(StartRecurringInvoiceMultiselect()),
appBarTitle: ListFilter(
entityType: EntityType.recurringInvoice,
entityIds: viewModel.recurringInvoiceList,
filter: state.recurringInvoiceListState.filter,
onFilterChanged: (value) {
store.dispatch(FilterRecurringInvoices(value));
},
),
body: RecurringInvoiceListBuilder(),
bottomNavigationBar: AppBottomBar(
entityType: EntityType.recurringInvoice,
tableColumns: RecurringInvoicePresenter.getAllTableFields(userCompany),
defaultTableColumns:
RecurringInvoicePresenter.getDefaultTableFields(userCompany),
onSelectedSortField: (value) {
store.dispatch(SortRecurringInvoices(value));
},
sortFields: [
InvoiceFields.invoiceNumber,
],
onSelectedState: (EntityState state, value) {
store.dispatch(FilterRecurringInvoicesByState(state));
},
onCheckboxPressed: () {
if (store.state.recurringInvoiceListState.isInMultiselect()) {
store.dispatch(ClearRecurringInvoiceMultiselect());
} else {
store.dispatch(StartRecurringInvoiceMultiselect());
}
},
customValues1: company.getCustomFieldValues(
CustomFieldType.invoice1,
excludeBlank: true),
customValues2: company.getCustomFieldValues(
CustomFieldType.invoice2,
excludeBlank: true),
customValues3: company.getCustomFieldValues(
CustomFieldType.invoice3,
excludeBlank: true),
customValues4: company.getCustomFieldValues(
CustomFieldType.invoice4,
excludeBlank: true),
onSelectedCustom1: (value) =>
store.dispatch(FilterRecurringInvoicesByCustom1(value)),
onSelectedCustom2: (value) =>
store.dispatch(FilterRecurringInvoicesByCustom2(value)),
onSelectedCustom3: (value) =>
store.dispatch(FilterRecurringInvoicesByCustom3(value)),
onSelectedCustom4: (value) =>
store.dispatch(FilterRecurringInvoicesByCustom4(value)),
),
floatingActionButton: state.prefState.isMenuFloated &&
userCompany.canCreate(EntityType.recurringInvoice)
? FloatingActionButton(
heroTag: 'recurring_invoice_fab',
backgroundColor: Theme.of(context).primaryColorDark,
onPressed: () {
createEntityByType(
context: context, entityType: EntityType.recurringInvoice);
},
child: Icon(
Icons.add,
color: Colors.white,
),
tooltip: localization.newRecurringInvoice,
)
: null,
);
}
}

View File

@ -0,0 +1,66 @@
import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_selectors.dart';
import 'package:redux/redux.dart';
import 'recurring_invoice_screen.dart';
class RecurringInvoiceScreenBuilder extends StatelessWidget {
const RecurringInvoiceScreenBuilder({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, RecurringInvoiceScreenVM>(
converter: RecurringInvoiceScreenVM.fromStore,
builder: (context, vm) {
return RecurringInvoiceScreen(
viewModel: vm,
);
},
);
}
}
class RecurringInvoiceScreenVM {
RecurringInvoiceScreenVM({
@required this.isInMultiselect,
@required this.recurringInvoiceList,
@required this.userCompany,
@required this.onEntityAction,
@required this.recurringInvoiceMap,
});
final bool isInMultiselect;
final UserCompanyEntity userCompany;
final List<String> recurringInvoiceList;
final Function(BuildContext, List<BaseEntity>, EntityAction) onEntityAction;
final BuiltMap<String, InvoiceEntity> recurringInvoiceMap;
static RecurringInvoiceScreenVM fromStore(Store<AppState> store) {
final state = store.state;
return RecurringInvoiceScreenVM(
recurringInvoiceMap: state.recurringInvoiceState.map,
recurringInvoiceList: memoizedFilteredRecurringInvoiceList(
state.uiState.filterEntityId,
state.uiState.filterEntityType,
state.recurringInvoiceState.map,
state.clientState.map,
state.recurringInvoiceState.list,
state.recurringInvoiceListState,
state.staticState,
state.userState.map),
userCompany: state.userCompany,
isInMultiselect: state.recurringInvoiceListState.isInMultiselect(),
onEntityAction: (BuildContext context, List<BaseEntity> recurringInvoices,
EntityAction action) =>
handleRecurringInvoiceAction(context, recurringInvoices, action),
);
}
}

View File

@ -0,0 +1,34 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/view_scaffold.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/view/recurring_invoice_view_vm.dart';
class RecurringInvoiceView extends StatefulWidget {
const RecurringInvoiceView({
Key key,
@required this.viewModel,
@required this.isFilter,
}) : super(key: key);
final RecurringInvoiceViewVM viewModel;
final bool isFilter;
@override
_RecurringInvoiceViewState createState() => new _RecurringInvoiceViewState();
}
class _RecurringInvoiceViewState extends State<RecurringInvoiceView> {
@override
Widget build(BuildContext context) {
final viewModel = widget.viewModel;
final recurringInvoice = viewModel.recurringInvoice;
return ViewScaffold(
isFilter: widget.isFilter,
entity: recurringInvoice,
body: ListView(
children: <Widget>[],
),
);
}
}

View File

@ -0,0 +1,87 @@
import 'dart:async';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:redux/redux.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/recurring_invoice/recurring_invoice_actions.dart';
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/view/recurring_invoice_view.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class RecurringInvoiceViewScreen extends StatelessWidget {
const RecurringInvoiceViewScreen({
Key key,
this.isFilter = false,
}) : super(key: key);
static const String route = '/recurring_invoice/view';
final bool isFilter;
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, RecurringInvoiceViewVM>(
converter: (Store<AppState> store) {
return RecurringInvoiceViewVM.fromStore(store);
},
builder: (context, vm) {
return RecurringInvoiceView(
viewModel: vm,
isFilter: isFilter,
);
},
);
}
}
class RecurringInvoiceViewVM {
RecurringInvoiceViewVM({
@required this.state,
@required this.recurringInvoice,
@required this.company,
@required this.onEntityAction,
@required this.onRefreshed,
@required this.isSaving,
@required this.isLoading,
@required this.isDirty,
});
factory RecurringInvoiceViewVM.fromStore(Store<AppState> store) {
final state = store.state;
final recurringInvoice = state.recurringInvoiceState
.map[state.recurringInvoiceUIState.selectedId] ??
InvoiceEntity(id: state.recurringInvoiceUIState.selectedId);
Future<Null> _handleRefresh(BuildContext context) {
final completer = snackBarCompleter<Null>(
context, AppLocalization.of(context).refreshComplete);
store.dispatch(LoadRecurringInvoice(
completer: completer, recurringInvoiceId: recurringInvoice.id));
return completer.future;
}
return RecurringInvoiceViewVM(
state: state,
company: state.company,
isSaving: state.isSaving,
isLoading: state.isLoading,
isDirty: recurringInvoice.isNew,
recurringInvoice: recurringInvoice,
onRefreshed: (context) => _handleRefresh(context),
onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [recurringInvoice], action,
autoPop: true),
);
}
final AppState state;
final InvoiceEntity recurringInvoice;
final CompanyEntity company;
final Function(BuildContext, EntityAction) onEntityAction;
final Function(BuildContext) onRefreshed;
final bool isSaving;
final bool isLoading;
final bool isDirty;
}

View File

@ -87,6 +87,7 @@ class _AccountManagementState extends State<AccountManagement>
// TODO change to kModules.keys
children: [
kModuleInvoices,
kModuleRecurringInvoices,
kModuleQuotes,
kModuleCredits,
].map((module) {

View File

@ -15,6 +15,17 @@ mixin LocalizationsProvider on LocaleCodeAware {
static final Map<String, Map<String, String>> _localizedValues = {
'en': {
// STARTER: lang key - do not remove comment
'recurring_invoice': 'Recurring Invoice',
'recurring_invoices': 'Recurring Invoices',
'new_recurring_invoice': 'New Recurring Invoice',
'edit_recurring_invoice': 'Edit Recurring Invoice',
'created_recurring_invoice': 'Successfully created recurring invoice',
'updated_recurring_invoice': 'Successfully updated recurring invoice',
'archived_recurring_invoice': 'Successfully archived recurring invoice',
'deleted_recurring_invoice': 'Successfully deleted recurring invoice',
'removed_recurring_invoice': 'Successfully removed recurring invoice',
'restored_recurring_invoice': 'Successfully restored recurring invoice',
'search_recurring_invoice': 'Search Recurring Invoice',
'send_date': 'Send Date',
'auto_bill_on': 'Auto Bill On',
'minimum_under_payment_amount': 'Minimum Under Payment Amount',
@ -312,7 +323,6 @@ mixin LocalizationsProvider on LocaleCodeAware {
'restored_design': 'Successfully restored design',
'proposals': 'Proposals',
'tickets': 'Tickets',
'recurring_invoices': 'Recurring Invoices',
'recurring_quotes': 'Recurring Quotes',
'recurring_tasks': 'Recurring Tasks',
'recurring_expenses': 'Recurring Expenses',
@ -953,7 +963,6 @@ mixin LocalizationsProvider on LocaleCodeAware {
'edit_expense': 'Edit Expense',
'edit_vendor': 'Edit Vendor',
'edit_project': 'Edit Project',
'edit_recurring_invoice': 'Edit Recurring Invoice',
'edit_recurring_expense': 'Edit Recurring Expense',
'edit_recurring_quote': 'Edit Recurring Quote',
'billing_address': 'Billing Address',
@ -3628,6 +3637,33 @@ mixin LocalizationsProvider on LocaleCodeAware {
String get appUpdated => _localizedValues[localeCode]['app_updated'] ?? '';
// STARTER: lang field - do not remove comment
String get recurringInvoice =>
_localizedValues[localeCode]['recurring_invoice'];
String get recurringInvoices =>
_localizedValues[localeCode]['recurring_invoices'];
String get newRecurringInvoice =>
_localizedValues[localeCode]['new_recurring_invoice'];
String get createdRecurringInvoice =>
_localizedValues[localeCode]['created_recurring_invoice'];
String get updatedRecurringInvoice =>
_localizedValues[localeCode]['updated_recurring_invoice'];
String get archivedRecurringInvoice =>
_localizedValues[localeCode]['archived_recurring_invoice'];
String get deletedRecurringInvoice =>
_localizedValues[localeCode]['deleted_recurring_invoice'];
String get restoredRecurringInvoice =>
_localizedValues[localeCode]['restored_recurring_invoice'];
String get searchRecurringInvoice =>
_localizedValues[localeCode]['search_recurring_invoice'];
String get webhook => _localizedValues[localeCode]['webhook'] ?? '';
String get webhooks => _localizedValues[localeCode]['webhooks'] ?? '';
@ -3744,9 +3780,6 @@ mixin LocalizationsProvider on LocaleCodeAware {
String get tickets => _localizedValues[localeCode]['tickets'] ?? '';
String get recurringInvoices =>
_localizedValues[localeCode]['recurring_invoices'] ?? '';
String get recurringQuotes =>
_localizedValues[localeCode]['recurring_quotes'] ?? '';

View File

@ -423,7 +423,7 @@ else
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/app/app_actions.dart
comment="STARTER: edit - do not remove comment"
code="case EntityType.${module_camel}: store.dispatch(Edit${Module}(${module_camel}: map[entityId], navigator: navigator, completer: completer ?? snackBarCompleter<${Module}Entity>(context, entity.isNew ? localization.created${Module} : localization.updated${Module}),));break;${lineBreak}"
code="case EntityType.${module_camel}: store.dispatch(Edit${Module}(${module_camel}: entity, navigator: navigator, completer: completer ?? snackBarCompleter<${Module}Entity>(context, entity.isNew ? localization.created${Module} : localization.updated${Module}),));break;${lineBreak}"
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/app/app_actions.dart
comment="STARTER: actions - do not remove comment"
@ -431,7 +431,7 @@ else
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/redux/app/app_actions.dart
comment="STARTER: lang key - do not remove comment"
code="'${module_snake}': '${Module}', '${module_snake}s': '${Module}s', 'new_${module_snake}': 'New ${Module}', 'edit_${module_snake}': 'Edit ${Module}', 'created_${module_snake}': 'Successfully created ${module_snake}', 'updated_${module_snake}': 'Successfully updated ${module_snake}', 'archived_${module_snake}': 'Successfully archived ${module_snake}', 'deleted_${module_snake}': 'Successfully deleted ${module_snake}', 'removed_${module_snake}': 'Successfully removed ${module_snake}', 'restored_${module_snake}': 'Successfully restored ${module_snake}',${lineBreak}, 'search_${module_snake}': 'Search ${Module}'"
code="'${module_snake}': '${Module}', '${module_snake}s': '${Module}s', 'new_${module_snake}': 'New ${Module}', 'edit_${module_snake}': 'Edit ${Module}', 'created_${module_snake}': 'Successfully created ${module_snake}', 'updated_${module_snake}': 'Successfully updated ${module_snake}', 'archived_${module_snake}': 'Successfully archived ${module_snake}', 'deleted_${module_snake}': 'Successfully deleted ${module_snake}', 'removed_${module_snake}': 'Successfully removed ${module_snake}', 'restored_${module_snake}': 'Successfully restored ${module_snake}', 'search_${module_snake}': 'Search ${Module}',${lineBreak}"
sed -i -e "s/$comment/$comment${lineBreak}$code/g" ./lib/utils/i18n.dart
comment="STARTER: lang field - do not remove comment"

View File

@ -26,14 +26,8 @@ class StubRepository {
return stubResponse.data;
}
Future<BuiltList<StubEntity>> loadList(
Credentials credentials, int updatedAt) async {
String url = credentials.url + '/stubs?';
if (updatedAt > 0) {
url += '&updated_at=${updatedAt - kUpdatedAtBufferSeconds}';
}
Future<BuiltList<StubEntity>> loadList(Credentials credentials) async {
final String url = credentials.url + '/stubs?';
final dynamic response = await webClient.get(url, credentials.token);
final StubListResponse stubResponse =

View File

@ -7,6 +7,7 @@ import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
class ViewStubList extends AbstractNavigatorAction implements PersistUI, StopLoading {
ViewStubList({
@ -245,6 +246,25 @@ class FilterStubsByCustom4 implements PersistUI {
final String value;
}
class StartStubMultiselect {
StartStubMultiselect();
}
class AddToStubMultiselect {
AddToStubMultiselect({@required this.entity});
final BaseEntity entity;
}
class RemoveFromStubMultiselect {
RemoveFromStubMultiselect({@required this.entity});
final BaseEntity entity;
}
class ClearStubMultiselect {
ClearStubMultiselect();
}
void handleStubAction(
BuildContext context, List<BaseEntity> stubs, EntityAction action) {
@ -254,6 +274,7 @@ void handleStubAction(
}
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final localization = AppLocalization.of(context);
final stub = stubs.first as StubEntity;
final stubIds = stubs.map((stub) => stub.id).toList();
@ -297,28 +318,7 @@ void handleStubAction(
showEntityActionsDialog(
entities: [stub],
context: context,
client: state.clientState.get(stub.clientId),
);
break;
}
}
class StartStubMultiselect {
StartStubMultiselect();
}
class AddToStubMultiselect {
AddToStubMultiselect({@required this.entity});
final BaseEntity entity;
}
class RemoveFromStubMultiselect {
RemoveFromStubMultiselect({@required this.entity});
final BaseEntity entity;
}
class ClearStubMultiselect {
ClearStubMultiselect();
}

View File

@ -44,11 +44,6 @@ Middleware<AppState> _editStub() {
final action = dynamicAction as EditStub;
if (!action.force && hasChanges(
store: store, context: action.context, callback: () => store.dispatch(action))) {
return;
}
next(action);
store.dispatch(UpdateCurrentRoute(StubEditScreen.route));
@ -64,12 +59,6 @@ Middleware<AppState> _viewStub() {
final action = dynamicAction as ViewStub;
if (!action.force && hasChanges(
store: store, context: action.context, callback: () => store.dispatch(action))) {
return;
}
next(action);
store.dispatch(UpdateCurrentRoute(StubViewScreen.route));
@ -84,17 +73,10 @@ Middleware<AppState> _viewStubList() {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as ViewStubList;
if (!action.force && hasChanges(
store: store, context: action.context, callback: () => store.dispatch(action))) {
return;
}
next(action);
if (store.state.staticState.isStale) {
store.dispatch(RefreshData());
} else if (store.state.stubState.isStale) {
store.dispatch(LoadStubs());
}
store.dispatch(UpdateCurrentRoute(StubScreen.route));
@ -237,16 +219,10 @@ Middleware<AppState> _loadStubs(StubRepository repository) {
final action = dynamicAction as LoadStubs;
final AppState state = store.state;
if (!state.stubState.isStale && !action.force) {
next(action);
return;
}
final int updatedAt = (state.stubState.lastUpdated / 1000).round();
store.dispatch(LoadStubsRequest());
repository
.loadList(state.credentials, updatedAt)
.loadList(state.credentials)
.then((data) {
store.dispatch(LoadStubsSuccess(data));

View File

@ -72,11 +72,6 @@ final stubListReducer = combineReducers<ListUIState>([
TypedReducer<ListUIState, RemoveFromStubMultiselect>(
_removeFromListMultiselect),
TypedReducer<ListUIState, ClearStubMultiselect>(_clearListMultiselect),
TypedReducer<ListUIState, ClearEntityFilter>(
(state, action) => state.rebuild((b) => b
..filterEntityId = null
..filterEntityType = null)),
]);
ListUIState _filterStubsByCustom1(

View File

@ -1,16 +1,22 @@
import 'package:invoiceninja_flutter/data/models/stub_model.dart';
import 'package:invoiceninja_flutter/redux/static/static_state.dart';
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 memoizedDropdownStubList = memo3(
var memoizedDropdownStubList = memo5(
(BuiltMap<String, StubEntity> stubMap, BuiltList<String> stubList,
StaticState staticState,
BuiltMap<String, UserEntity> userMap,
String clientId) =>
dropdownStubsSelector(stubMap, stubList, clientId));
dropdownStubsSelector(stubMap, stubList, staticState, userMap, clientId));
List<String> dropdownStubsSelector(BuiltMap<String, StubEntity> stubMap,
BuiltList<String> stubList, String clientId) {
BuiltList<String> stubList,
StaticState staticState,
BuiltMap<String, UserEntity> userMap,
String clientId) {
final list = stubList.where((stubId) {
final stub = stubMap[stubId];
/*
@ -30,16 +36,21 @@ List<String> dropdownStubsSelector(BuiltMap<String, StubEntity> stubMap,
return list;
}
var memoizedFilteredStubList = memo3((BuiltMap<String, StubEntity> stubMap,
BuiltList<String> stubList, ListUIState stubListState) =>
filteredStubsSelector(stubMap, stubList, stubListState));
var memoizedFilteredStubList = memo7((BuiltMap<String, StubEntity> stubMap,
BuiltList<String> stubList,
StaticState staticState,
BuiltMap<String, UserEntity> userMap,
ListUIState stubListState) =>
filteredStubsSelector(stubMap, stubList, staticState, userMap, stubListState));
List<String> filteredStubsSelector(BuiltMap<String, StubEntity> stubMap,
BuiltList<String> stubList, ListUIState stubListState) {
BuiltList<String> stubList,
StaticState staticState,
BuiltMap<String, UserEntity> userMap,
ListUIState stubListState) {
final list = stubList.where((stubId) {
final stub = stubMap[stubId];
if (stubListState.filterEntityId != null &&
stub.id != stubListState.filterEntityId) {
if (filterEntityId != null && stub.id != filterEntityId) {
return false;
} else {
@ -60,12 +71,17 @@ List<String> filteredStubsSelector(BuiltMap<String, StubEntity> stubMap,
}).toList();
list.sort((stubAId, stubBId) {
final stubA = stubMap[stubAId];
final stubB = stubMap[stubBId];
return stubA.compareTo(
stubB, stubListState.sortField, stubListState.sortAscending);
return stubMap[stubAId].compareTo(
stub: stubMap[stubBId],
sortField: stubListState.sortField,
sortAscending: stubListState.sortAscending,
//clientMap: clientMap,
staticState: staticState,
userMap: userMap,
);
});
return list;
}

View File

@ -12,7 +12,6 @@ import 'package:invoiceninja_flutter/ui/app/dismissible_entity.dart';
class StubListItem extends StatelessWidget {
const StubListItem({
@required this.user,
@required this.onEntityAction,
@required this.stub,
@required this.filter,
this.onTap,
@ -22,7 +21,6 @@ class StubListItem extends StatelessWidget {
});
final UserEntity user;
final Function(EntityAction) onEntityAction;
final GestureTapCallback onTap;
final GestureTapCallback onLongPress;
final StubEntity stub;
@ -50,7 +48,6 @@ class StubListItem extends StatelessWidget {
entity: stub,
isSelected: stub.id ==
(uiState.isEditing ? stubUIState.editing.id : stubUIState.selectedId),
onEntityAction: onEntityAction,
child: ListTile(
onTap: () => onTap != null
? onTap()

View File

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:invoiceninja_flutter/data/models/stub_model.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/tables/entity_list.dart';
import 'package:invoiceninja_flutter/ui/webhook/stub_list_item.dart';
import 'package:invoiceninja_flutter/ui/stub/stub_list_item.dart';
import 'package:invoiceninja_flutter/ui/stub/stub_presenter.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
@ -18,7 +18,6 @@ import 'package:invoiceninja_flutter/redux/stub/stub_selectors.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/stub/stub_actions.dart';
import 'package:invoiceninja_flutter/utils/platforms.dart';
class StubListBuilder extends StatelessWidget {
const StubListBuilder({Key key}) : super(key: key);
@ -33,7 +32,6 @@ class StubListBuilder extends StatelessWidget {
presenter: StubPresenter(),
state: viewModel.state,
entityList: viewModel.stubList,
onEntityTap: viewModel.onStubTap,
tableColumns: viewModel.tableColumns,
onRefreshed: viewModel.onRefreshed,
onSortColumn: viewModel.onSortColumn,
@ -49,17 +47,6 @@ class StubListBuilder extends StatelessWidget {
user: viewModel.state.user,
filter: viewModel.filter,
stub: stub,
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
showEntityActionsDialog(
entities: [stub],
context: context,
);
} else {
handleStubAction(context, [stub], action);
}
},
onTap: () => viewModel.onStubTap(context, stub),
isChecked: isInMultiselect && listState.isSelected(stub.id),
);
});

View File

@ -36,7 +36,6 @@ class StubScreen extends StatelessWidget {
entityType: EntityType.stub,
onHamburgerLongPress: () => store.dispatch(StartStubMultiselect()),
appBarTitle: ListFilter(
placeholder: localization.searchStubs,
entityIds: viewModel.stubList,
filter: state.stubListState.filter,
onFilterChanged: (value) {

View File

@ -1,7 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/stub/view/stub_view_vm.dart';
import 'package:invoiceninja_flutter/ui/app/form_card.dart';
import 'package:invoiceninja_flutter/ui/app/view_scaffold.dart';
class StubView extends StatefulWidget {