Subscriptions

This commit is contained in:
Hillel Coren 2021-03-24 21:51:52 +02:00
parent 9dd186cfca
commit ffc558903d
19 changed files with 234 additions and 198 deletions

View File

@ -129,9 +129,9 @@ part 'serializers.g.dart';
TaxRateItemResponse, TaxRateItemResponse,
TaxRateListResponse, TaxRateListResponse,
// STARTER: serializers - do not remove comment // STARTER: serializers - do not remove comment
SubscriptionEntity, SubscriptionEntity,
SubscriptionListResponse, SubscriptionListResponse,
SubscriptionItemResponse, SubscriptionItemResponse,
TaskStatusEntity, TaskStatusEntity,
TaskStatusListResponse, TaskStatusListResponse,

View File

@ -8,7 +8,6 @@ import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/data/web_client.dart'; import 'package:invoiceninja_flutter/data/web_client.dart';
class SubscriptionRepository { class SubscriptionRepository {
const SubscriptionRepository({ const SubscriptionRepository({
this.webClient = const WebClient(), this.webClient = const WebClient(),
}); });
@ -20,52 +19,53 @@ class SubscriptionRepository {
final dynamic response = await webClient.get( final dynamic response = await webClient.get(
'${credentials.url}/subscriptions/$entityId', credentials.token); '${credentials.url}/subscriptions/$entityId', credentials.token);
final SubscriptionItemResponse subscriptionResponse = final SubscriptionItemResponse subscriptionResponse = serializers
serializers.deserializeWith(SubscriptionItemResponse.serializer, response); .deserializeWith(SubscriptionItemResponse.serializer, response);
return subscriptionResponse.data; return subscriptionResponse.data;
} }
Future<BuiltList<SubscriptionEntity>> loadList(Credentials credentials) async { Future<BuiltList<SubscriptionEntity>> loadList(
Credentials credentials) async {
final String url = credentials.url + '/subscriptions?'; final String url = credentials.url + '/subscriptions?';
final dynamic response = await webClient.get(url, credentials.token); final dynamic response = await webClient.get(url, credentials.token);
final SubscriptionListResponse subscriptionResponse = final SubscriptionListResponse subscriptionResponse = serializers
serializers.deserializeWith(SubscriptionListResponse.serializer, response); .deserializeWith(SubscriptionListResponse.serializer, response);
return subscriptionResponse.data; return subscriptionResponse.data;
} }
Future<List<SubscriptionEntity>> bulkAction( Future<List<SubscriptionEntity>> bulkAction(
Credentials credentials, List<String> ids, EntityAction action) async { Credentials credentials, List<String> ids, EntityAction action) async {
final url = credentials.url + '/subscriptions/bulk'; final url = credentials.url + '/subscriptions/bulk';
final dynamic response = await webClient.post(url, credentials.token, final dynamic response = await webClient.post(url, credentials.token,
data: json.encode({'ids': ids, 'action': action.toApiParam()})); data: json.encode({'ids': ids, 'action': action.toApiParam()}));
final SubscriptionListResponse subscriptionResponse = final SubscriptionListResponse subscriptionResponse = serializers
serializers.deserializeWith(SubscriptionListResponse.serializer, response); .deserializeWith(SubscriptionListResponse.serializer, response);
return subscriptionResponse.data.toList();
}
return subscriptionResponse.data.toList();
}
Future<SubscriptionEntity> saveData( Future<SubscriptionEntity> saveData(
Credentials credentials, SubscriptionEntity subscription) async { Credentials credentials, SubscriptionEntity subscription) async {
final data = serializers.serializeWith(SubscriptionEntity.serializer, subscription); final data =
serializers.serializeWith(SubscriptionEntity.serializer, subscription);
dynamic response; dynamic response;
if (subscription.isNew) { if (subscription.isNew) {
response = await webClient.post( response = await webClient.post(
credentials.url + '/subscriptions', credentials.url + '/subscriptions', credentials.token,
credentials.token,
data: json.encode(data)); data: json.encode(data));
} else { } else {
final url = '${credentials.url}/subscriptions/${subscription.id}'; final url = '${credentials.url}/subscriptions/${subscription.id}';
response = await webClient.put(url, credentials.token, data: json.encode(data)); response =
await webClient.put(url, credentials.token, data: json.encode(data));
} }
final SubscriptionItemResponse subscriptionResponse = final SubscriptionItemResponse subscriptionResponse = serializers
serializers.deserializeWith(SubscriptionItemResponse.serializer, response); .deserializeWith(SubscriptionItemResponse.serializer, response);
return subscriptionResponse.data; return subscriptionResponse.data;
} }

View File

@ -69,8 +69,7 @@ void main({bool isTesting = false}) async {
..addAll(createStoreSettingsMiddleware()) ..addAll(createStoreSettingsMiddleware())
..addAll(createStoreReportsMiddleware()) ..addAll(createStoreReportsMiddleware())
// STARTER: middleware - do not remove comment // STARTER: middleware - do not remove comment
..addAll(createStoreSubscriptionsMiddleware()) ..addAll(createStoreSubscriptionsMiddleware())
..addAll(createStoreTaskStatusesMiddleware()) ..addAll(createStoreTaskStatusesMiddleware())
..addAll(createStoreExpenseCategoriesMiddleware()) ..addAll(createStoreExpenseCategoriesMiddleware())
..addAll(createStoreRecurringInvoicesMiddleware()) ..addAll(createStoreRecurringInvoicesMiddleware())

View File

@ -343,9 +343,12 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
QuoteEmailScreen.route: (context) => QuoteEmailScreen(), QuoteEmailScreen.route: (context) => QuoteEmailScreen(),
QuotePdfScreen.route: (context) => QuotePdfScreen(), QuotePdfScreen.route: (context) => QuotePdfScreen(),
// STARTER: routes - do not remove comment // STARTER: routes - do not remove comment
SubscriptionScreen.route: (context) => SubscriptionScreenBuilder(), SubscriptionScreen.route: (context) =>
SubscriptionViewScreen.route: (context) => SubscriptionViewScreen(), SubscriptionScreenBuilder(),
SubscriptionEditScreen.route: (context) => SubscriptionEditScreen(), SubscriptionViewScreen.route: (context) =>
SubscriptionViewScreen(),
SubscriptionEditScreen.route: (context) =>
SubscriptionEditScreen(),
TaskStatusScreen.route: (context) => TaskStatusScreen.route: (context) =>
TaskStatusScreenBuilder(), TaskStatusScreenBuilder(),

View File

@ -100,7 +100,9 @@ final lastErrorReducer = combineReducers<String>([
return '${action.error}'; return '${action.error}';
}), }),
// STARTER: errors - do not remove comment // STARTER: errors - do not remove comment
TypedReducer<String, LoadSubscriptionsFailure>((state, action) { return '${action.error}'; }), TypedReducer<String, LoadSubscriptionsFailure>((state, action) {
return '${action.error}';
}),
TypedReducer<String, LoadTaskStatusesFailure>((state, action) { TypedReducer<String, LoadTaskStatusesFailure>((state, action) {
return '${action.error}'; return '${action.error}';

View File

@ -51,7 +51,9 @@ import 'package:invoiceninja_flutter/ui/group/edit/group_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/product/edit/product_edit_vm.dart'; import 'package:invoiceninja_flutter/ui/product/edit/product_edit_vm.dart';
// STARTER: import - do not remove comment // STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/redux/subscription/subscription_state.dart';import 'package:invoiceninja_flutter/ui/subscription/edit/subscription_edit_vm.dart';import 'package:invoiceninja_flutter/redux/subscription/subscription_selectors.dart'; import 'package:invoiceninja_flutter/redux/subscription/subscription_state.dart';
import 'package:invoiceninja_flutter/ui/subscription/edit/subscription_edit_vm.dart';
import 'package:invoiceninja_flutter/redux/subscription/subscription_selectors.dart';
import 'package:invoiceninja_flutter/redux/task_status/task_status_state.dart'; import 'package:invoiceninja_flutter/redux/task_status/task_status_state.dart';
import 'package:invoiceninja_flutter/ui/recurring_invoice/recurring_invoice_screen.dart'; import 'package:invoiceninja_flutter/ui/recurring_invoice/recurring_invoice_screen.dart';
@ -275,8 +277,8 @@ abstract class AppState implements Built<AppState, AppStateBuilder> {
case EntityType.invoice: case EntityType.invoice:
return invoiceState.map; return invoiceState.map;
// STARTER: states switch map - do not remove comment // STARTER: states switch map - do not remove comment
case EntityType.subscription: case EntityType.subscription:
return subscriptionState.map; return subscriptionState.map;
case EntityType.taskStatus: case EntityType.taskStatus:
return taskStatusState.map; return taskStatusState.map;
@ -349,8 +351,8 @@ return subscriptionState.map;
case EntityType.invoice: case EntityType.invoice:
return invoiceState.list; return invoiceState.list;
// STARTER: states switch list - do not remove comment // STARTER: states switch list - do not remove comment
case EntityType.subscription: case EntityType.subscription:
return subscriptionState.list; return subscriptionState.list;
case EntityType.taskStatus: case EntityType.taskStatus:
return taskStatusState.list; return taskStatusState.list;
@ -414,8 +416,8 @@ return subscriptionState.list;
case EntityType.invoice: case EntityType.invoice:
return invoiceUIState; return invoiceUIState;
// STARTER: states switch - do not remove comment // STARTER: states switch - do not remove comment
case EntityType.subscription: case EntityType.subscription:
return subscriptionUIState; return subscriptionUIState;
case EntityType.taskStatus: case EntityType.taskStatus:
return taskStatusUIState; return taskStatusUIState;
@ -483,10 +485,10 @@ return subscriptionUIState;
ListUIState get invoiceListState => uiState.invoiceUIState.listUIState; ListUIState get invoiceListState => uiState.invoiceUIState.listUIState;
// STARTER: state getters - do not remove comment // STARTER: state getters - do not remove comment
SubscriptionState get subscriptionState => userCompanyState.subscriptionState; SubscriptionState get subscriptionState => userCompanyState.subscriptionState;
ListUIState get subscriptionListState => uiState.subscriptionUIState.listUIState; ListUIState get subscriptionListState =>
SubscriptionUIState get subscriptionUIState => uiState.subscriptionUIState; uiState.subscriptionUIState.listUIState;
SubscriptionUIState get subscriptionUIState => uiState.subscriptionUIState;
TaskStatusState get taskStatusState => userCompanyState.taskStatusState; TaskStatusState get taskStatusState => userCompanyState.taskStatusState;
@ -644,7 +646,9 @@ SubscriptionUIState get subscriptionUIState => uiState.subscriptionUIState;
case CreditEditScreen.route: case CreditEditScreen.route:
return hasCreditChanges(creditUIState.editing, creditState.map); return hasCreditChanges(creditUIState.editing, creditState.map);
// STARTER: has changes - do not remove comment // STARTER: has changes - do not remove comment
case SubscriptionEditScreen.route: return hasSubscriptionChanges(subscriptionUIState.editing, subscriptionState.map); case SubscriptionEditScreen.route:
return hasSubscriptionChanges(
subscriptionUIState.editing, subscriptionState.map);
case TaskStatusEditScreen.route: case TaskStatusEditScreen.route:
return hasTaskStatusChanges( return hasTaskStatusChanges(

View File

@ -52,8 +52,8 @@ UserCompanyState companyReducer(UserCompanyState state, dynamic action) {
..vendorState.replace(vendorsReducer(state.vendorState, action)) ..vendorState.replace(vendorsReducer(state.vendorState, action))
..taskState.replace(tasksReducer(state.taskState, action)) ..taskState.replace(tasksReducer(state.taskState, action))
// STARTER: reducer - do not remove comment // STARTER: reducer - do not remove comment
..subscriptionState.replace(subscriptionsReducer(state.subscriptionState, action)) ..subscriptionState
.replace(subscriptionsReducer(state.subscriptionState, action))
..taskStatusState ..taskStatusState
.replace(taskStatusesReducer(state.taskStatusState, action)) .replace(taskStatusesReducer(state.taskStatusState, action))
..expenseCategoryState ..expenseCategoryState

View File

@ -52,7 +52,7 @@ abstract class UserCompanyState
paymentState: PaymentState(), paymentState: PaymentState(),
quoteState: QuoteState(), quoteState: QuoteState(),
// STARTER: constructor - do not remove comment // STARTER: constructor - do not remove comment
subscriptionState: SubscriptionState(), subscriptionState: SubscriptionState(),
taskStatusState: TaskStatusState(), taskStatusState: TaskStatusState(),
@ -103,7 +103,7 @@ subscriptionState: SubscriptionState(),
QuoteState get quoteState; QuoteState get quoteState;
// STARTER: fields - do not remove comment // STARTER: fields - do not remove comment
SubscriptionState get subscriptionState; SubscriptionState get subscriptionState;
TaskStatusState get taskStatusState; TaskStatusState get taskStatusState;

View File

@ -9,7 +9,8 @@ import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart'; import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
class ViewSubscriptionList extends AbstractNavigatorAction implements PersistUI, StopLoading { class ViewSubscriptionList extends AbstractNavigatorAction
implements PersistUI, StopLoading {
ViewSubscriptionList({ ViewSubscriptionList({
@required NavigatorState navigator, @required NavigatorState navigator,
this.force = false, this.force = false,
@ -120,7 +121,6 @@ class LoadSubscriptionsSuccess implements StopLoading {
} }
} }
class SaveSubscriptionRequest implements StartSaving { class SaveSubscriptionRequest implements StartSaving {
SaveSubscriptionRequest({this.completer, this.subscription}); SaveSubscriptionRequest({this.completer, this.subscription});
@ -141,7 +141,7 @@ class AddSubscriptionSuccess implements StopSaving, PersistData, PersistUI {
} }
class SaveSubscriptionFailure implements StopSaving { class SaveSubscriptionFailure implements StopSaving {
SaveSubscriptionFailure (this.error); SaveSubscriptionFailure(this.error);
final Object error; final Object error;
} }
@ -203,7 +203,6 @@ class RestoreSubscriptionsFailure implements StopSaving {
final List<SubscriptionEntity> subscriptions; final List<SubscriptionEntity> subscriptions;
} }
class FilterSubscriptions implements PersistUI { class FilterSubscriptions implements PersistUI {
FilterSubscriptions(this.filter); FilterSubscriptions(this.filter);
@ -274,7 +273,6 @@ class UpdateSubscriptionTab implements PersistUI {
void handleSubscriptionAction( void handleSubscriptionAction(
BuildContext context, List<BaseEntity> subscriptions, EntityAction action) { BuildContext context, List<BaseEntity> subscriptions, EntityAction action) {
if (subscriptions.isEmpty) { if (subscriptions.isEmpty) {
return; return;
} }
@ -283,7 +281,8 @@ void handleSubscriptionAction(
final state = store.state; final state = store.state;
final localization = AppLocalization.of(context); final localization = AppLocalization.of(context);
final subscription = subscriptions.first as SubscriptionEntity; final subscription = subscriptions.first as SubscriptionEntity;
final subscriptionIds = subscriptions.map((subscription) => subscription.id).toList(); final subscriptionIds =
subscriptions.map((subscription) => subscription.id).toList();
switch (action) { switch (action) {
case EntityAction.edit: case EntityAction.edit:
@ -291,15 +290,18 @@ void handleSubscriptionAction(
break; break;
case EntityAction.restore: case EntityAction.restore:
store.dispatch(RestoreSubscriptionsRequest( store.dispatch(RestoreSubscriptionsRequest(
snackBarCompleter<Null>(context, localization.restoredSubscription), subscriptionIds)); snackBarCompleter<Null>(context, localization.restoredSubscription),
subscriptionIds));
break; break;
case EntityAction.archive: case EntityAction.archive:
store.dispatch(ArchiveSubscriptionsRequest( store.dispatch(ArchiveSubscriptionsRequest(
snackBarCompleter<Null>(context, localization.archivedSubscription), subscriptionIds)); snackBarCompleter<Null>(context, localization.archivedSubscription),
subscriptionIds));
break; break;
case EntityAction.delete: case EntityAction.delete:
store.dispatch(DeleteSubscriptionsRequest( store.dispatch(DeleteSubscriptionsRequest(
snackBarCompleter<Null>(context, localization.deletedSubscription), subscriptionIds)); snackBarCompleter<Null>(context, localization.deletedSubscription),
subscriptionIds));
break; break;
case EntityAction.toggleMultiselect: case EntityAction.toggleMultiselect:
if (!store.state.subscriptionListState.isInMultiselect()) { if (!store.state.subscriptionListState.isInMultiselect()) {
@ -312,23 +314,21 @@ void handleSubscriptionAction(
for (final subscription in subscriptions) { for (final subscription in subscriptions) {
if (!store.state.subscriptionListState.isSelected(subscription.id)) { if (!store.state.subscriptionListState.isSelected(subscription.id)) {
store.dispatch( store.dispatch(AddToSubscriptionMultiselect(entity: subscription));
AddToSubscriptionMultiselect(entity: subscription));
} else { } else {
store.dispatch( store.dispatch(
RemoveFromSubscriptionMultiselect(entity: subscription)); RemoveFromSubscriptionMultiselect(entity: subscription));
} }
} }
break; break;
case EntityAction.more: case EntityAction.more:
showEntityActionsDialog( showEntityActionsDialog(
entities: [subscription], entities: [subscription],
context: context, context: context,
); );
break; break;
default: default:
print('Error: unhandled action $action in subscription_actions'); print('Error: unhandled action $action in subscription_actions');
break; break;
} }
} }

View File

@ -41,7 +41,6 @@ List<Middleware<AppState>> createStoreSubscriptionsMiddleware([
Middleware<AppState> _editSubscription() { Middleware<AppState> _editSubscription() {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) { return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as EditSubscription; final action = dynamicAction as EditSubscription;
next(action); next(action);
@ -49,22 +48,22 @@ Middleware<AppState> _editSubscription() {
store.dispatch(UpdateCurrentRoute(SubscriptionEditScreen.route)); store.dispatch(UpdateCurrentRoute(SubscriptionEditScreen.route));
if (isMobile(action.context)) { if (isMobile(action.context)) {
action.navigator.pushNamed(SubscriptionEditScreen.route); action.navigator.pushNamed(SubscriptionEditScreen.route);
} }
}; };
} }
Middleware<AppState> _viewSubscription() { Middleware<AppState> _viewSubscription() {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) async { return (Store<AppState> store, dynamic dynamicAction,
NextDispatcher next) async {
final action = dynamicAction as ViewSubscription; final action = dynamicAction as ViewSubscription;
next(action); next(action);
store.dispatch(UpdateCurrentRoute(SubscriptionViewScreen.route)); store.dispatch(UpdateCurrentRoute(SubscriptionViewScreen.route));
if (isMobile(action.context)) { if (isMobile(action.context)) {
Navigator.of(action.context).pushNamed(SubscriptionViewScreen.route); Navigator.of(action.context).pushNamed(SubscriptionViewScreen.route);
} }
}; };
} }
@ -91,11 +90,12 @@ Middleware<AppState> _viewSubscriptionList() {
Middleware<AppState> _archiveSubscription(SubscriptionRepository repository) { Middleware<AppState> _archiveSubscription(SubscriptionRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) { return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as ArchiveSubscriptionsRequest; final action = dynamicAction as ArchiveSubscriptionsRequest;
final prevSubscriptions = final prevSubscriptions = action.subscriptionIds
action.subscriptionIds.map((id) => store.state.subscriptionState.map[id]).toList(); .map((id) => store.state.subscriptionState.map[id])
.toList();
repository repository
.bulkAction( .bulkAction(store.state.credentials, action.subscriptionIds,
store.state.credentials, action.subscriptionIds, EntityAction.archive) EntityAction.archive)
.then((List<SubscriptionEntity> subscriptions) { .then((List<SubscriptionEntity> subscriptions) {
store.dispatch(ArchiveSubscriptionsSuccess(subscriptions)); store.dispatch(ArchiveSubscriptionsSuccess(subscriptions));
if (action.completer != null) { if (action.completer != null) {
@ -116,11 +116,12 @@ Middleware<AppState> _archiveSubscription(SubscriptionRepository repository) {
Middleware<AppState> _deleteSubscription(SubscriptionRepository repository) { Middleware<AppState> _deleteSubscription(SubscriptionRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) { return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as DeleteSubscriptionsRequest; final action = dynamicAction as DeleteSubscriptionsRequest;
final prevSubscriptions = final prevSubscriptions = action.subscriptionIds
action.subscriptionIds.map((id) => store.state.subscriptionState.map[id]).toList(); .map((id) => store.state.subscriptionState.map[id])
.toList();
repository repository
.bulkAction( .bulkAction(store.state.credentials, action.subscriptionIds,
store.state.credentials, action.subscriptionIds, EntityAction.delete) EntityAction.delete)
.then((List<SubscriptionEntity> subscriptions) { .then((List<SubscriptionEntity> subscriptions) {
store.dispatch(DeleteSubscriptionsSuccess(subscriptions)); store.dispatch(DeleteSubscriptionsSuccess(subscriptions));
if (action.completer != null) { if (action.completer != null) {
@ -141,11 +142,12 @@ Middleware<AppState> _deleteSubscription(SubscriptionRepository repository) {
Middleware<AppState> _restoreSubscription(SubscriptionRepository repository) { Middleware<AppState> _restoreSubscription(SubscriptionRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) { return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as RestoreSubscriptionsRequest; final action = dynamicAction as RestoreSubscriptionsRequest;
final prevSubscriptions = final prevSubscriptions = action.subscriptionIds
action.subscriptionIds.map((id) => store.state.subscriptionState.map[id]).toList(); .map((id) => store.state.subscriptionState.map[id])
.toList();
repository repository
.bulkAction( .bulkAction(store.state.credentials, action.subscriptionIds,
store.state.credentials, action.subscriptionIds, EntityAction.restore) EntityAction.restore)
.then((List<SubscriptionEntity> subscriptions) { .then((List<SubscriptionEntity> subscriptions) {
store.dispatch(RestoreSubscriptionsSuccess(subscriptions)); store.dispatch(RestoreSubscriptionsSuccess(subscriptions));
if (action.completer != null) { if (action.completer != null) {
@ -167,8 +169,7 @@ Middleware<AppState> _saveSubscription(SubscriptionRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) { return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as SaveSubscriptionRequest; final action = dynamicAction as SaveSubscriptionRequest;
repository repository
.saveData( .saveData(store.state.credentials, action.subscription)
store.state.credentials, action.subscription)
.then((SubscriptionEntity subscription) { .then((SubscriptionEntity subscription) {
if (action.subscription.isNew) { if (action.subscription.isNew) {
store.dispatch(AddSubscriptionSuccess(subscription)); store.dispatch(AddSubscriptionSuccess(subscription));
@ -177,7 +178,6 @@ Middleware<AppState> _saveSubscription(SubscriptionRepository repository) {
} }
action.completer.complete(subscription); action.completer.complete(subscription);
}).catchError((Object error) { }).catchError((Object error) {
print(error); print(error);
store.dispatch(SaveSubscriptionFailure(error)); store.dispatch(SaveSubscriptionFailure(error));
@ -190,10 +190,10 @@ Middleware<AppState> _saveSubscription(SubscriptionRepository repository) {
Middleware<AppState> _loadSubscription(SubscriptionRepository repository) { Middleware<AppState> _loadSubscription(SubscriptionRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) { return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as LoadSubscription; final action = dynamicAction as LoadSubscription;
final AppState state = store.state; final AppState state = store.state;
store.dispatch(LoadSubscriptionRequest()); store.dispatch(LoadSubscriptionRequest());
repository repository
.loadItem(state.credentials, action.subscriptionId) .loadItem(state.credentials, action.subscriptionId)
.then((subscription) { .then((subscription) {
@ -216,14 +216,11 @@ store.dispatch(LoadSubscriptionRequest());
Middleware<AppState> _loadSubscriptions(SubscriptionRepository repository) { Middleware<AppState> _loadSubscriptions(SubscriptionRepository repository) {
return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) { return (Store<AppState> store, dynamic dynamicAction, NextDispatcher next) {
final action = dynamicAction as LoadSubscriptions; final action = dynamicAction as LoadSubscriptions;
final AppState state = store.state; final AppState state = store.state;
store.dispatch(LoadSubscriptionsRequest()); store.dispatch(LoadSubscriptionsRequest());
repository repository.loadList(state.credentials).then((data) {
.loadList(state.credentials)
.then((data) {
store.dispatch(LoadSubscriptionsSuccess(data)); store.dispatch(LoadSubscriptionsSuccess(data));
if (action.completer != null) { if (action.completer != null) {

View File

@ -14,8 +14,7 @@ EntityUIState subscriptionUIReducer(SubscriptionUIState state, dynamic action) {
..listUIState.replace(subscriptionListReducer(state.listUIState, action)) ..listUIState.replace(subscriptionListReducer(state.listUIState, action))
..editing.replace(editingReducer(state.editing, action)) ..editing.replace(editingReducer(state.editing, action))
..selectedId = selectedIdReducer(state.selectedId, action) ..selectedId = selectedIdReducer(state.selectedId, action)
..tabIndex = tabIndexReducer(state.tabIndex, action) ..tabIndex = tabIndexReducer(state.tabIndex, action));
);
} }
final tabIndexReducer = combineReducers<int>([ final tabIndexReducer = combineReducers<int>([
@ -36,55 +35,66 @@ Reducer<String> selectedIdReducer = combineReducers([
(String selectedId, dynamic action) => action.subscriptionId), (String selectedId, dynamic action) => action.subscriptionId),
TypedReducer<String, AddSubscriptionSuccess>( TypedReducer<String, AddSubscriptionSuccess>(
(String selectedId, dynamic action) => action.subscription.id), (String selectedId, dynamic action) => action.subscription.id),
TypedReducer<String, SelectCompany>((selectedId, action) => action.clearSelection ? '' : selectedId), TypedReducer<String, SelectCompany>(
(selectedId, action) => action.clearSelection ? '' : selectedId),
TypedReducer<String, ClearEntityFilter>((selectedId, action) => ''), TypedReducer<String, ClearEntityFilter>((selectedId, action) => ''),
TypedReducer<String, FilterByEntity>((selectedId, action) => TypedReducer<String, FilterByEntity>(
action (selectedId, action) => action.clearSelection
.clearSelection ? ''
? '' : action.entityType == EntityType.subscription
: action.entityType == EntityType.subscription ? action.entityId : selectedId), ? action.entityId
: selectedId),
]); ]);
final editingReducer = combineReducers<SubscriptionEntity>([ final editingReducer = combineReducers<SubscriptionEntity>([
TypedReducer<SubscriptionEntity, SaveSubscriptionSuccess>(_updateEditing), TypedReducer<SubscriptionEntity, SaveSubscriptionSuccess>(_updateEditing),
TypedReducer<SubscriptionEntity, AddSubscriptionSuccess>(_updateEditing), TypedReducer<SubscriptionEntity, AddSubscriptionSuccess>(_updateEditing),
TypedReducer<SubscriptionEntity, RestoreSubscriptionsSuccess>((subscriptions, action) { TypedReducer<SubscriptionEntity, RestoreSubscriptionsSuccess>(
return action.subscriptions[0]; (subscriptions, action) {
}), return action.subscriptions[0];
TypedReducer<SubscriptionEntity, ArchiveSubscriptionsSuccess>((subscriptions, action) { }),
return action.subscriptions[0]; TypedReducer<SubscriptionEntity, ArchiveSubscriptionsSuccess>(
}), (subscriptions, action) {
TypedReducer<SubscriptionEntity, DeleteSubscriptionsSuccess>((subscriptions, action) { return action.subscriptions[0];
return action.subscriptions[0]; }),
}), TypedReducer<SubscriptionEntity, DeleteSubscriptionsSuccess>(
(subscriptions, action) {
return action.subscriptions[0];
}),
TypedReducer<SubscriptionEntity, EditSubscription>(_updateEditing), TypedReducer<SubscriptionEntity, EditSubscription>(_updateEditing),
TypedReducer<SubscriptionEntity, UpdateSubscription>((subscription, action) { TypedReducer<SubscriptionEntity, UpdateSubscription>((subscription, action) {
return action.subscription.rebuild((b) => b..isChanged = true); return action.subscription.rebuild((b) => b..isChanged = true);
}), }),
TypedReducer<SubscriptionEntity, DiscardChanges>(_clearEditing), TypedReducer<SubscriptionEntity, DiscardChanges>(_clearEditing),
]); ]);
SubscriptionEntity _clearEditing(SubscriptionEntity subscription, dynamic action) { SubscriptionEntity _clearEditing(
SubscriptionEntity subscription, dynamic action) {
return SubscriptionEntity(); return SubscriptionEntity();
} }
SubscriptionEntity _updateEditing(SubscriptionEntity subscription, dynamic action) { SubscriptionEntity _updateEditing(
SubscriptionEntity subscription, dynamic action) {
return action.subscription; return action.subscription;
} }
final subscriptionListReducer = combineReducers<ListUIState>([ final subscriptionListReducer = combineReducers<ListUIState>([
TypedReducer<ListUIState, SortSubscriptions>(_sortSubscriptions), TypedReducer<ListUIState, SortSubscriptions>(_sortSubscriptions),
TypedReducer<ListUIState, FilterSubscriptionsByState>(_filterSubscriptionsByState), TypedReducer<ListUIState, FilterSubscriptionsByState>(
_filterSubscriptionsByState),
TypedReducer<ListUIState, FilterSubscriptions>(_filterSubscriptions), TypedReducer<ListUIState, FilterSubscriptions>(_filterSubscriptions),
TypedReducer<ListUIState, FilterSubscriptionsByCustom1>(_filterSubscriptionsByCustom1), TypedReducer<ListUIState, FilterSubscriptionsByCustom1>(
TypedReducer<ListUIState, FilterSubscriptionsByCustom2>(_filterSubscriptionsByCustom2), _filterSubscriptionsByCustom1),
TypedReducer<ListUIState, StartSubscriptionMultiselect>(_startListMultiselect), TypedReducer<ListUIState, FilterSubscriptionsByCustom2>(
TypedReducer<ListUIState, AddToSubscriptionMultiselect>(_addToListMultiselect), _filterSubscriptionsByCustom2),
TypedReducer<ListUIState, StartSubscriptionMultiselect>(
_startListMultiselect),
TypedReducer<ListUIState, AddToSubscriptionMultiselect>(
_addToListMultiselect),
TypedReducer<ListUIState, RemoveFromSubscriptionMultiselect>( TypedReducer<ListUIState, RemoveFromSubscriptionMultiselect>(
_removeFromListMultiselect), _removeFromListMultiselect),
TypedReducer<ListUIState, ClearSubscriptionMultiselect>(_clearListMultiselect), TypedReducer<ListUIState, ClearSubscriptionMultiselect>(
_clearListMultiselect),
]); ]);
ListUIState _filterSubscriptionsByCustom1( ListUIState _filterSubscriptionsByCustom1(
@ -93,7 +103,8 @@ ListUIState _filterSubscriptionsByCustom1(
return subscriptionListState return subscriptionListState
.rebuild((b) => b..custom1Filters.remove(action.value)); .rebuild((b) => b..custom1Filters.remove(action.value));
} else { } else {
return subscriptionListState.rebuild((b) => b..custom1Filters.add(action.value)); return subscriptionListState
.rebuild((b) => b..custom1Filters.add(action.value));
} }
} }
@ -103,27 +114,33 @@ ListUIState _filterSubscriptionsByCustom2(
return subscriptionListState return subscriptionListState
.rebuild((b) => b..custom2Filters.remove(action.value)); .rebuild((b) => b..custom2Filters.remove(action.value));
} else { } else {
return subscriptionListState.rebuild((b) => b..custom2Filters.add(action.value)); return subscriptionListState
.rebuild((b) => b..custom2Filters.add(action.value));
} }
} }
ListUIState _filterSubscriptionsByState( ListUIState _filterSubscriptionsByState(
ListUIState subscriptionListState, FilterSubscriptionsByState action) { ListUIState subscriptionListState, FilterSubscriptionsByState action) {
if (subscriptionListState.stateFilters.contains(action.state)) { if (subscriptionListState.stateFilters.contains(action.state)) {
return subscriptionListState.rebuild((b) => b..stateFilters.remove(action.state)); return subscriptionListState
.rebuild((b) => b..stateFilters.remove(action.state));
} else { } else {
return subscriptionListState.rebuild((b) => b..stateFilters.add(action.state)); return subscriptionListState
.rebuild((b) => b..stateFilters.add(action.state));
} }
} }
ListUIState _filterSubscriptions(ListUIState subscriptionListState, FilterSubscriptions action) { ListUIState _filterSubscriptions(
return subscriptionListState.rebuild((b) => b..filter = action.filter ListUIState subscriptionListState, FilterSubscriptions action) {
..filterClearedAt = action.filter == null return subscriptionListState.rebuild((b) => b
? DateTime.now().millisecondsSinceEpoch ..filter = action.filter
: subscriptionListState.filterClearedAt); ..filterClearedAt = action.filter == null
? DateTime.now().millisecondsSinceEpoch
: subscriptionListState.filterClearedAt);
} }
ListUIState _sortSubscriptions(ListUIState subscriptionListState, SortSubscriptions action) { ListUIState _sortSubscriptions(
ListUIState subscriptionListState, SortSubscriptions action) {
return subscriptionListState.rebuild((b) => b return subscriptionListState.rebuild((b) => b
..sortAscending = b.sortField != action.field || !b.sortAscending ..sortAscending = b.sortField != action.field || !b.sortAscending
..sortField = action.field); ..sortField = action.field);
@ -136,8 +153,7 @@ ListUIState _startListMultiselect(
ListUIState _addToListMultiselect( ListUIState _addToListMultiselect(
ListUIState productListState, AddToSubscriptionMultiselect action) { ListUIState productListState, AddToSubscriptionMultiselect action) {
return productListState return productListState.rebuild((b) => b..selectedIds.add(action.entity.id));
.rebuild((b) => b..selectedIds.add(action.entity.id));
} }
ListUIState _removeFromListMultiselect( ListUIState _removeFromListMultiselect(
@ -154,12 +170,17 @@ ListUIState _clearListMultiselect(
final subscriptionsReducer = combineReducers<SubscriptionState>([ final subscriptionsReducer = combineReducers<SubscriptionState>([
TypedReducer<SubscriptionState, SaveSubscriptionSuccess>(_updateSubscription), TypedReducer<SubscriptionState, SaveSubscriptionSuccess>(_updateSubscription),
TypedReducer<SubscriptionState, AddSubscriptionSuccess>(_addSubscription), TypedReducer<SubscriptionState, AddSubscriptionSuccess>(_addSubscription),
TypedReducer<SubscriptionState, LoadSubscriptionsSuccess>(_setLoadedSubscriptions), TypedReducer<SubscriptionState, LoadSubscriptionsSuccess>(
TypedReducer<SubscriptionState, LoadSubscriptionSuccess>(_setLoadedSubscription), _setLoadedSubscriptions),
TypedReducer<SubscriptionState, LoadSubscriptionSuccess>(
_setLoadedSubscription),
TypedReducer<SubscriptionState, LoadCompanySuccess>(_setLoadedCompany), TypedReducer<SubscriptionState, LoadCompanySuccess>(_setLoadedCompany),
TypedReducer<SubscriptionState, ArchiveSubscriptionsSuccess>(_archiveSubscriptionSuccess), TypedReducer<SubscriptionState, ArchiveSubscriptionsSuccess>(
TypedReducer<SubscriptionState, DeleteSubscriptionsSuccess>(_deleteSubscriptionSuccess), _archiveSubscriptionSuccess),
TypedReducer<SubscriptionState, RestoreSubscriptionsSuccess>(_restoreSubscriptionSuccess), TypedReducer<SubscriptionState, DeleteSubscriptionsSuccess>(
_deleteSubscriptionSuccess),
TypedReducer<SubscriptionState, RestoreSubscriptionsSuccess>(
_restoreSubscriptionSuccess),
]); ]);
SubscriptionState _archiveSubscriptionSuccess( SubscriptionState _archiveSubscriptionSuccess(
@ -189,21 +210,23 @@ SubscriptionState _restoreSubscriptionSuccess(
}); });
} }
SubscriptionState _addSubscription(SubscriptionState subscriptionState, AddSubscriptionSuccess action) { SubscriptionState _addSubscription(
SubscriptionState subscriptionState, AddSubscriptionSuccess action) {
return subscriptionState.rebuild((b) => b return subscriptionState.rebuild((b) => b
..map[action.subscription.id] = action.subscription ..map[action.subscription.id] = action.subscription
..list.add(action.subscription.id)); ..list.add(action.subscription.id));
} }
SubscriptionState _updateSubscription(SubscriptionState subscriptionState, SaveSubscriptionSuccess action) { SubscriptionState _updateSubscription(
return subscriptionState.rebuild((b) => b SubscriptionState subscriptionState, SaveSubscriptionSuccess action) {
..map[action.subscription.id] = action.subscription); return subscriptionState
.rebuild((b) => b..map[action.subscription.id] = action.subscription);
} }
SubscriptionState _setLoadedSubscription( SubscriptionState _setLoadedSubscription(
SubscriptionState subscriptionState, LoadSubscriptionSuccess action) { SubscriptionState subscriptionState, LoadSubscriptionSuccess action) {
return subscriptionState.rebuild((b) => b return subscriptionState
..map[action.subscription.id] = action.subscription); .rebuild((b) => b..map[action.subscription.id] = action.subscription);
} }
SubscriptionState _setLoadedSubscriptions( SubscriptionState _setLoadedSubscriptions(
@ -211,7 +234,7 @@ SubscriptionState _setLoadedSubscriptions(
subscriptionState.loadSubscriptions(action.subscriptions); subscriptionState.loadSubscriptions(action.subscriptions);
SubscriptionState _setLoadedCompany( SubscriptionState _setLoadedCompany(
SubscriptionState subscriptionState, LoadCompanySuccess action) { SubscriptionState subscriptionState, LoadCompanySuccess action) {
final company = action.userCompany.company; final company = action.userCompany.company;
return subscriptionState.loadSubscriptions(company.subscriptions); return subscriptionState.loadSubscriptions(company.subscriptions);
} }

View File

@ -329,7 +329,17 @@ Reducer<BuiltList<HistoryRecord>> historyReducer = combineReducers([
_addToHistory(historyList, _addToHistory(historyList,
HistoryRecord(id: action.group.id, entityType: EntityType.group))), HistoryRecord(id: action.group.id, entityType: EntityType.group))),
// STARTER: history - do not remove comment // STARTER: history - do not remove comment
TypedReducer<BuiltList<HistoryRecord>, ViewSubscription>((historyList, action) => _addToHistory(historyList, HistoryRecord(id: action.subscriptionId, entityType: EntityType.subscription))),TypedReducer<BuiltList<HistoryRecord>, EditSubscription>((historyList, action) => _addToHistory(historyList, HistoryRecord(id: action.subscription.id, entityType: EntityType.subscription))), TypedReducer<BuiltList<HistoryRecord>, ViewSubscription>(
(historyList, action) => _addToHistory(
historyList,
HistoryRecord(
id: action.subscriptionId, entityType: EntityType.subscription))),
TypedReducer<BuiltList<HistoryRecord>, EditSubscription>(
(historyList, action) => _addToHistory(
historyList,
HistoryRecord(
id: action.subscription.id,
entityType: EntityType.subscription))),
TypedReducer<BuiltList<HistoryRecord>, ViewTaskStatus>( TypedReducer<BuiltList<HistoryRecord>, ViewTaskStatus>(
(historyList, action) => _addToHistory( (historyList, action) => _addToHistory(

View File

@ -88,8 +88,8 @@ UIState uiReducer(UIState state, dynamic action) {
.replace(dashboardUIReducer(state.dashboardUIState, action)) .replace(dashboardUIReducer(state.dashboardUIState, action))
..reportsUIState.replace(reportsUIReducer(state.reportsUIState, action)) ..reportsUIState.replace(reportsUIReducer(state.reportsUIState, action))
// STARTER: reducer - do not remove comment // STARTER: reducer - do not remove comment
..subscriptionUIState.replace(subscriptionUIReducer(state.subscriptionUIState, action)) ..subscriptionUIState
.replace(subscriptionUIReducer(state.subscriptionUIState, action))
..taskStatusUIState ..taskStatusUIState
.replace(taskStatusUIReducer(state.taskStatusUIState, action)) .replace(taskStatusUIReducer(state.taskStatusUIState, action))
..expenseCategoryUIState ..expenseCategoryUIState

View File

@ -52,7 +52,7 @@ abstract class UIState implements Built<UIState, UIStateBuilder> {
clientUIState: ClientUIState(), clientUIState: ClientUIState(),
invoiceUIState: InvoiceUIState(), invoiceUIState: InvoiceUIState(),
// STARTER: constructor - do not remove comment // STARTER: constructor - do not remove comment
subscriptionUIState: SubscriptionUIState(), subscriptionUIState: SubscriptionUIState(),
taskStatusUIState: TaskStatusUIState(), taskStatusUIState: TaskStatusUIState(),
expenseCategoryUIState: ExpenseCategoryUIState(), expenseCategoryUIState: ExpenseCategoryUIState(),
@ -112,7 +112,7 @@ subscriptionUIState: SubscriptionUIState(),
InvoiceUIState get invoiceUIState; InvoiceUIState get invoiceUIState;
// STARTER: properties - do not remove comment // STARTER: properties - do not remove comment
SubscriptionUIState get subscriptionUIState; SubscriptionUIState get subscriptionUIState;
TaskStatusUIState get taskStatusUIState; TaskStatusUIState get taskStatusUIState;

View File

@ -386,13 +386,6 @@ class MenuDrawer extends StatelessWidget {
iconTooltip: localization.newExpense, iconTooltip: localization.newExpense,
), ),
// STARTER: menu - do not remove comment // STARTER: menu - do not remove comment
DrawerTile(
company: company,
entityType: EntityType.subscription,
icon: getEntityIcon(EntityType.subscription),
title: localization.subscriptions,
),
DrawerTile( DrawerTile(
company: company, company: company,
icon: getEntityIcon(EntityType.reports), icon: getEntityIcon(EntityType.reports),

View File

@ -115,8 +115,7 @@ class LoginVM {
}) async { }) async {
try { try {
await GoogleOAuth.signOut(); await GoogleOAuth.signOut();
final signedIn = await GoogleOAuth.signIn( final signedIn = await GoogleOAuth.signIn((idToken, accessToken) {
(idToken, accessToken) {
if (idToken.isEmpty || accessToken.isEmpty) { if (idToken.isEmpty || accessToken.isEmpty) {
GoogleOAuth.signOut(); GoogleOAuth.signOut();
completer.completeError( completer.completeError(

View File

@ -63,26 +63,31 @@ class SubscriptionEditVM {
onChanged: (SubscriptionEntity subscription) { onChanged: (SubscriptionEntity subscription) {
store.dispatch(UpdateSubscription(subscription)); store.dispatch(UpdateSubscription(subscription));
}, },
onCancelPressed: (BuildContext context) { onCancelPressed: (BuildContext context) {
createEntity(context: context, entity: SubscriptionEntity(), force: true); createEntity(
}, context: context, entity: SubscriptionEntity(), force: true);
},
onSavePressed: (BuildContext context) { onSavePressed: (BuildContext context) {
final localization = AppLocalization.of(context); final localization = AppLocalization.of(context);
final Completer<SubscriptionEntity> completer = new Completer<SubscriptionEntity>(); final Completer<SubscriptionEntity> completer =
store.dispatch(SaveSubscriptionRequest(completer: completer, subscription: subscription)); new Completer<SubscriptionEntity>();
store.dispatch(SaveSubscriptionRequest(
completer: completer, subscription: subscription));
return completer.future.then((savedSubscription) { return completer.future.then((savedSubscription) {
showToast(subscription.isNew showToast(subscription.isNew
? localization.createdSubscription ? localization.createdSubscription
: localization.updatedSubscription); : localization.updatedSubscription);
if (isMobile(context)) { if (isMobile(context)) {
store.dispatch(UpdateCurrentRoute(SubscriptionViewScreen.route)); store.dispatch(UpdateCurrentRoute(SubscriptionViewScreen.route));
if (subscription.isNew) { if (subscription.isNew) {
Navigator.of(context).pushReplacementNamed(SubscriptionViewScreen.route); Navigator.of(context)
} else { .pushReplacementNamed(SubscriptionViewScreen.route);
Navigator.of(context).pop(savedSubscription); } else {
} Navigator.of(context).pop(savedSubscription);
}
} else { } else {
viewEntity(context: context, entity: savedSubscription, force: true); viewEntity(
context: context, entity: savedSubscription, force: true);
} }
}).catchError((Object error) { }).catchError((Object error) {
showDialog<ErrorDialog>( showDialog<ErrorDialog>(

View File

@ -3,11 +3,8 @@ import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart'; import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
class SubscriptionPresenter extends EntityPresenter { class SubscriptionPresenter extends EntityPresenter {
static List<String> getDefaultTableFields(UserCompanyEntity userCompany) { static List<String> getDefaultTableFields(UserCompanyEntity userCompany) {
return [ return [];
];
} }
static List<String> getAllTableFields(UserCompanyEntity userCompany) { static List<String> getAllTableFields(UserCompanyEntity userCompany) {

View File

@ -13,7 +13,10 @@ import 'package:invoiceninja_flutter/ui/subscription/view/subscription_view.dart
import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/app/app_state.dart';
class SubscriptionViewScreen extends StatelessWidget { class SubscriptionViewScreen extends StatelessWidget {
const SubscriptionViewScreen({Key key, this.isFilter = false,}) : super(key: key); const SubscriptionViewScreen({
Key key,
this.isFilter = false,
}) : super(key: key);
static const String route = '/subscription/view'; static const String route = '/subscription/view';
final bool isFilter; final bool isFilter;
@ -34,7 +37,6 @@ class SubscriptionViewScreen extends StatelessWidget {
} }
class SubscriptionViewVM { class SubscriptionViewVM {
SubscriptionViewVM({ SubscriptionViewVM({
@required this.state, @required this.state,
@required this.subscription, @required this.subscription,
@ -48,27 +50,29 @@ class SubscriptionViewVM {
factory SubscriptionViewVM.fromStore(Store<AppState> store) { factory SubscriptionViewVM.fromStore(Store<AppState> store) {
final state = store.state; final state = store.state;
final subscription = state.subscriptionState.map[state.subscriptionUIState.selectedId] ?? final subscription =
SubscriptionEntity(id: state.subscriptionUIState.selectedId); state.subscriptionState.map[state.subscriptionUIState.selectedId] ??
SubscriptionEntity(id: state.subscriptionUIState.selectedId);
Future<Null> _handleRefresh(BuildContext context) { Future<Null> _handleRefresh(BuildContext context) {
final completer = snackBarCompleter<Null>( final completer = snackBarCompleter<Null>(
context, AppLocalization.of(context).refreshComplete); context, AppLocalization.of(context).refreshComplete);
store.dispatch(LoadSubscription(completer: completer, subscriptionId: subscription.id)); store.dispatch(LoadSubscription(
completer: completer, subscriptionId: subscription.id));
return completer.future; return completer.future;
} }
return SubscriptionViewVM( return SubscriptionViewVM(
state: state, state: state,
company: state.company, company: state.company,
isSaving: state.isSaving, isSaving: state.isSaving,
isLoading: state.isLoading, isLoading: state.isLoading,
isDirty: subscription.isNew, isDirty: subscription.isNew,
subscription: subscription, subscription: subscription,
onRefreshed: (context) => _handleRefresh(context), onRefreshed: (context) => _handleRefresh(context),
onEntityAction: (BuildContext context, EntityAction action) => onEntityAction: (BuildContext context, EntityAction action) =>
handleEntitiesActions(context, [subscription], action, autoPop: true), handleEntitiesActions(context, [subscription], action, autoPop: true),
); );
} }
final AppState state; final AppState state;