Refactor entity actions

This commit is contained in:
Hillel Coren 2019-06-16 14:39:03 +03:00
parent c3237fd659
commit ccb07c4373
54 changed files with 958 additions and 1421 deletions

View File

@ -106,8 +106,6 @@ class ExpenseStatusColors {
};
}
const List<int> kPaymentTerms = [0, -1, 7, 10, 14, 15, 30, 60, 90];
const String kDesignCustom1 = 'Custom 1';

View File

@ -68,7 +68,11 @@ abstract class ExpenseEntity extends Object
with BaseEntity, SelectableEntity, BelongsToClient
implements Built<ExpenseEntity, ExpenseEntityBuilder> {
factory ExpenseEntity(
{int id, CompanyEntity company, UIState uiState, VendorEntity vendor, ClientEntity client}) {
{int id,
CompanyEntity company,
UIState uiState,
VendorEntity vendor,
ClientEntity client}) {
return _$ExpenseEntity._(
id: id ?? --ExpenseEntity.counter,
privateNotes: '',

View File

@ -120,8 +120,6 @@ abstract class CurrencyEntity extends Object
@override
double get listDisplayAmount => null;
static Serializer<CurrencyEntity> get serializer =>
_$currencyEntitySerializer;
}

View File

@ -311,7 +311,8 @@ Middleware<AppState> _createAccountLoaded() {
};
}
Middleware<AppState> _createPersistStatic(PersistenceRepository staticRepository) {
Middleware<AppState> _createPersistStatic(
PersistenceRepository staticRepository) {
return (Store<AppState> store, dynamic action, NextDispatcher next) {
// first process the action so the data is in the state
next(action);

View File

@ -13,7 +13,6 @@ var memoizedDropdownExpenseCategoriesList = memo2(
List<int> dropdownExpenseCategoriesSelector(
BuiltMap<int, ExpenseCategoryEntity> categoryMap,
BuiltList<ExpenseCategoryEntity> categoryList) {
final list = categoryList
//.where((category) => category.isActive)
.map((category) => category.id)

View File

@ -3,6 +3,13 @@ import 'package:flutter/widgets.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_selectors.dart';
class ViewExpenseList implements PersistUI {
ViewExpenseList(this.context);
@ -227,3 +234,47 @@ class FilterExpensesByEntity implements PersistUI {
final int entityId;
final EntityType entityType;
}
void handleExpenseAction(
BuildContext context, ExpenseEntity expense, EntityAction action) {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final CompanyEntity company = state.selectedCompany;
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.edit:
store.dispatch(EditExpense(context: context, expense: expense));
break;
case EntityAction.clone:
store.dispatch(EditExpense(context: context, expense: expense.clone));
break;
case EntityAction.newInvoice:
final item = convertExpenseToInvoiceItem(expense: expense);
store.dispatch(EditInvoice(
invoice: InvoiceEntity(company: company).rebuild((b) => b
..hasExpenses = true
..clientId = expense.clientId
..invoiceItems.add(item)),
context: context));
break;
case EntityAction.viewInvoice:
store.dispatch(
ViewInvoice(invoiceId: expense.invoiceId, context: context));
break;
case EntityAction.restore:
store.dispatch(RestoreExpenseRequest(
snackBarCompleter(context, localization.restoredExpense),
expense.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveExpenseRequest(
snackBarCompleter(context, localization.archivedExpense),
expense.id));
break;
case EntityAction.delete:
store.dispatch(DeleteExpenseRequest(
snackBarCompleter(context, localization.deletedExpense), expense.id));
break;
}
}

View File

@ -89,13 +89,13 @@ ListUIState _filterExpensesByState(
ListUIState _filterExpensesByStatus(
ListUIState expenseListState, FilterExpensesByStatus action) {
if (expenseListState.statusFilters.contains(action.status)) {
return expenseListState.rebuild((b) => b..statusFilters.remove(action.status));
return expenseListState
.rebuild((b) => b..statusFilters.remove(action.status));
} else {
return expenseListState.rebuild((b) => b..statusFilters.add(action.status));
}
}
ListUIState _filterExpenses(
ListUIState expenseListState, FilterExpenses action) {
return expenseListState.rebuild((b) => b..filter = action.filter);

View File

@ -1,9 +1,17 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_actions.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
import 'package:invoiceninja_flutter/utils/pdf.dart';
import 'package:url_launcher/url_launcher.dart';
class ViewInvoiceList implements PersistUI {
ViewInvoiceList(this.context);
@ -302,3 +310,63 @@ class FilterInvoicesByCustom2 implements PersistUI {
final String value;
}
void handleInvoiceAction(
BuildContext context, InvoiceEntity invoice, EntityAction action) async {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final CompanyEntity company = state.selectedCompany;
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.edit:
store.dispatch(EditInvoice(context: context, invoice: invoice));
break;
case EntityAction.pdf:
viewPdf(invoice, context);
break;
case EntityAction.clientPortal:
if (await canLaunch(invoice.invitationSilentLink)) {
await launch(invoice.invitationSilentLink,
forceSafariVC: false, forceWebView: false);
}
break;
case EntityAction.markSent:
store.dispatch(MarkSentInvoiceRequest(
snackBarCompleter(context, localization.markedInvoiceAsSent),
invoice.id));
break;
case EntityAction.sendEmail:
store.dispatch(ShowEmailInvoice(
completer: snackBarCompleter(context, localization.emailedInvoice),
invoice: invoice,
context: context));
break;
case EntityAction.cloneToInvoice:
store.dispatch(
EditInvoice(context: context, invoice: invoice.cloneToInvoice));
break;
case EntityAction.cloneToQuote:
store.dispatch(EditQuote(context: context, quote: invoice.cloneToQuote));
break;
case EntityAction.enterPayment:
store.dispatch(EditPayment(
context: context,
payment: invoice.createPayment(company)));
break;
case EntityAction.restore:
store.dispatch(RestoreInvoiceRequest(
snackBarCompleter(context, localization.restoredInvoice),
invoice.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveInvoiceRequest(
snackBarCompleter(context, localization.archivedInvoice),
invoice.id));
break;
case EntityAction.delete:
store.dispatch(DeleteInvoiceRequest(
snackBarCompleter(context, localization.deletedInvoice), invoice.id));
break;
}
}

View File

@ -77,7 +77,8 @@ InvoiceEntity _addInvoiceItem(InvoiceEntity invoice, AddInvoiceItem action) {
InvoiceEntity _addInvoiceItems(InvoiceEntity invoice, AddInvoiceItems action) {
return invoice.rebuild((b) => b
..hasTasks = action.invoiceItems.where((item) => item.isTask).isNotEmpty
..hasExpenses = action.invoiceItems.where((item) => item.isExpense).isNotEmpty
..hasExpenses =
action.invoiceItems.where((item) => item.isExpense).isNotEmpty
..invoiceItems.addAll(action.invoiceItems));
}

View File

@ -3,6 +3,10 @@ import 'package:flutter/widgets.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class ViewPaymentList implements PersistUI {
ViewPaymentList(this.context);
@ -236,3 +240,33 @@ class FilterPaymentsByEntity implements PersistUI {
final int entityId;
final EntityType entityType;
}
void handlePaymentAction(
BuildContext context, PaymentEntity payment, EntityAction action) {
final store = StoreProvider.of<AppState>(context);
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.edit:
store.dispatch(EditPayment(context: context, payment: payment));
break;
case EntityAction.sendEmail:
store.dispatch(EmailPaymentRequest(
snackBarCompleter(context, localization.emailedPayment), payment));
break;
case EntityAction.restore:
store.dispatch(RestorePaymentRequest(
snackBarCompleter(context, localization.restoredPayment),
payment.id));
break;
case EntityAction.archive:
store.dispatch(ArchivePaymentRequest(
snackBarCompleter(context, localization.archivedPayment),
payment.id));
break;
case EntityAction.delete:
store.dispatch(DeletePaymentRequest(
snackBarCompleter(context, localization.deletedPayment), payment.id));
break;
}
}

View File

@ -10,7 +10,6 @@ import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class ViewProductList implements PersistUI {
ViewProductList(this.context);
@ -179,7 +178,6 @@ class FilterProductDropdown {
final String filter;
}
void handleProductAction(
BuildContext context, ProductEntity product, EntityAction action) {
final store = StoreProvider.of<AppState>(context);
@ -189,7 +187,7 @@ void handleProductAction(
switch (action) {
case EntityAction.newInvoice:
final item =
convertProductToInvoiceItem(context: context, product: product);
convertProductToInvoiceItem(context: context, product: product);
store.dispatch(EditInvoice(
context: context,
invoice: InvoiceEntity(company: state.selectedCompany)
@ -199,8 +197,7 @@ void handleProductAction(
store.dispatch(EditProduct(context: context, product: product));
break;
case EntityAction.clone:
store.dispatch(
EditProduct(context: context, product: product.clone));
store.dispatch(EditProduct(context: context, product: product.clone));
break;
case EntityAction.restore:
store.dispatch(RestoreProductRequest(
@ -214,8 +211,7 @@ void handleProductAction(
break;
case EntityAction.delete:
store.dispatch(DeleteProductRequest(
snackBarCompleter(context, localization.deletedProduct),
product.id));
snackBarCompleter(context, localization.deletedProduct), product.id));
break;
}
}

View File

@ -3,6 +3,13 @@ import 'package:flutter/widgets.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/redux/project/project_selectors.dart';
class ViewProjectList implements PersistUI {
ViewProjectList(this.context);
@ -221,3 +228,47 @@ class FilterProjectsByEntity implements PersistUI {
final int entityId;
final EntityType entityType;
}
void handleProjectAction(
BuildContext context, ProjectEntity project, EntityAction action) {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final CompanyEntity company = state.selectedCompany;
switch (action) {
case EntityAction.edit:
store.dispatch(EditProject(context: context, project: project));
break;
case EntityAction.newInvoice:
final items =
convertProjectToInvoiceItem(project: project, context: context);
store.dispatch(EditInvoice(
invoice: InvoiceEntity(company: company).rebuild((b) => b
..hasTasks = true
..clientId = project.clientId
..invoiceItems.addAll(items)),
context: context));
break;
case EntityAction.clone:
store.dispatch(EditProject(context: context, project: project.clone));
break;
case EntityAction.restore:
store.dispatch(RestoreProjectRequest(
snackBarCompleter(
context, AppLocalization.of(context).restoredProject),
project.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveProjectRequest(
snackBarCompleter(
context, AppLocalization.of(context).archivedProject),
project.id));
break;
case EntityAction.delete:
store.dispatch(DeleteProjectRequest(
snackBarCompleter(
context, AppLocalization.of(context).deletedProject),
project.id));
break;
}
}

View File

@ -1,9 +1,16 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:flutter_redux/flutter_redux.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/utils/pdf.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:flutter/material.dart';
class ViewQuoteList implements PersistUI {
ViewQuoteList(this.context);
@ -322,3 +329,65 @@ class ConvertQuoteFailure implements StopSaving {
final dynamic error;
}
Future handleQuoteAction(
BuildContext context, InvoiceEntity quote, EntityAction action) async {
final store = StoreProvider.of<AppState>(context);
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.edit:
store.dispatch(EditQuote(context: context, quote: quote));
break;
case EntityAction.pdf:
viewPdf(quote, context);
break;
case EntityAction.clientPortal:
if (await canLaunch(quote.invitationSilentLink)) {
await launch(quote.invitationSilentLink,
forceSafariVC: false, forceWebView: false);
}
break;
case EntityAction.viewInvoice:
store.dispatch(
ViewInvoice(context: context, invoiceId: quote.quoteInvoiceId));
break;
case EntityAction.convert:
final Completer<InvoiceEntity> completer = Completer<InvoiceEntity>();
store.dispatch(ConvertQuote(completer, quote.id));
completer.future.then((InvoiceEntity invoice) {
store.dispatch(ViewInvoice(invoiceId: invoice.id, context: context));
});
break;
case EntityAction.markSent:
store.dispatch(MarkSentQuoteRequest(
snackBarCompleter(context, localization.markedQuoteAsSent),
quote.id));
break;
case EntityAction.sendEmail:
store.dispatch(ShowEmailQuote(
completer: snackBarCompleter(context, localization.emailedQuote),
quote: quote,
context: context));
break;
case EntityAction.cloneToInvoice:
store.dispatch(
EditInvoice(context: context, invoice: quote.cloneToInvoice));
break;
case EntityAction.cloneToQuote:
store.dispatch(EditQuote(context: context, quote: quote.cloneToQuote));
break;
case EntityAction.restore:
store.dispatch(RestoreQuoteRequest(
snackBarCompleter(context, localization.restoredQuote), quote.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveQuoteRequest(
snackBarCompleter(context, localization.archivedQuote), quote.id));
break;
case EntityAction.delete:
store.dispatch(DeleteQuoteRequest(
snackBarCompleter(context, localization.deletedQuote), quote.id));
break;
}
}

View File

@ -3,6 +3,15 @@ import 'package:flutter/widgets.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/redux/task/task_selectors.dart';
class ViewTaskList implements PersistUI {
ViewTaskList(this.context);
@ -259,3 +268,73 @@ class FilterTasksByEntity implements PersistUI {
final int entityId;
final EntityType entityType;
}
void handleTaskAction(
BuildContext context, TaskEntity task, EntityAction action) {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final CompanyEntity company = state.selectedCompany;
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.edit:
store.dispatch(EditTask(context: context, task: task));
break;
case EntityAction.start:
case EntityAction.stop:
case EntityAction.resume:
final Completer<TaskEntity> completer = new Completer<TaskEntity>();
final localization = AppLocalization.of(context);
store
.dispatch(SaveTaskRequest(completer: completer, task: task.toggle()));
completer.future.then((savedTask) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: savedTask.isRunning
? (savedTask.duration > 0
? localization.resumedTask
: localization.startedTask)
: localization.stoppedTask,
)));
}).catchError((Object error) {
showDialog<ErrorDialog>(
context: context,
builder: (BuildContext context) {
return ErrorDialog(error);
});
});
break;
case EntityAction.newInvoice:
final item = convertTaskToInvoiceItem(task: task, context: context);
store.dispatch(EditInvoice(
invoice:
InvoiceEntity(company: company).rebuild((b) => b
..hasTasks = true
..clientId = task.clientId
..invoiceItems.add(item)),
context: context));
break;
case EntityAction.viewInvoice:
store.dispatch(ViewInvoice(invoiceId: task.invoiceId, context: context));
break;
case EntityAction.clone:
store.dispatch(EditTask(context: context, task: task.clone));
break;
case EntityAction.restore:
store.dispatch(RestoreTaskRequest(
snackBarCompleter(context, localization.restoredTask),
task.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveTaskRequest(
snackBarCompleter(context, localization.archivedTask),
task.id));
break;
case EntityAction.delete:
store.dispatch(DeleteTaskRequest(
snackBarCompleter(context, localization.deletedTask),
task.id));
break;
}
}

View File

@ -3,6 +3,11 @@ import 'package:flutter/widgets.dart';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_actions.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_actions.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
class ViewVendorList implements PersistUI {
ViewVendorList(this.context);
@ -251,3 +256,34 @@ class FilterVendorsByEntity implements PersistUI {
final int entityId;
final EntityType entityType;
}
void handleVendorAction(
BuildContext context, VendorEntity vendor, EntityAction action) {
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final CompanyEntity company = state.selectedCompany;
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.edit:
store.dispatch(EditVendor(context: context, vendor: vendor));
break;
case EntityAction.newExpense:
store.dispatch(EditExpense(
expense: ExpenseEntity(company: company, vendor: vendor),
context: context));
break;
case EntityAction.restore:
store.dispatch(RestoreVendorRequest(
snackBarCompleter(context, localization.restoredVendor), vendor.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveVendorRequest(
snackBarCompleter(context, localization.archivedVendor), vendor.id));
break;
case EntityAction.delete:
store.dispatch(DeleteVendorRequest(
snackBarCompleter(context, localization.deletedVendor), vendor.id));
break;
}
}

View File

@ -47,27 +47,26 @@ class ClientList extends StatelessWidget {
final clientId = viewModel.clientList[index];
final client = viewModel.clientMap[clientId];
final user = viewModel.user;
void showDialog() => showEntityActionsDialog(
entity: client,
context: context,
user: user,
onEntityAction: viewModel.onEntityAction);
return ClientListItem(
user: viewModel.user,
filter: viewModel.filter,
client: client,
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
showEntityActionsDialog(
entity: client,
context: context,
user: user,
onEntityAction: viewModel.onEntityAction);
showDialog();
} else {
viewModel.onEntityAction(context, client, action);
}
},
onTap: () => viewModel.onClientTap(context, client),
onLongPress: () => showEntityActionsDialog(
entity: client,
context: context,
user: user,
onEntityAction: viewModel.onEntityAction),
onLongPress: () => showDialog(),
);
}),
);

View File

@ -59,11 +59,13 @@ class ClientListItem extends StatelessWidget {
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
filterMatch != null ? Text(
filterMatch,
maxLines: 3,
overflow: TextOverflow.ellipsis,
) : SizedBox(),
filterMatch != null
? Text(
filterMatch,
maxLines: 3,
overflow: TextOverflow.ellipsis,
)
: SizedBox(),
EntityStateLabel(client),
],
),

View File

@ -80,7 +80,7 @@ class ClientListVM {
onRefreshed: (context) => _handleRefresh(context),
onEntityAction:
(BuildContext context, BaseEntity client, EntityAction action) =>
handleClientAction(context, client, action),
handleClientAction(context, client, action),
);
}
}

View File

@ -290,7 +290,7 @@ class _CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
user: viewModel.company.user,
isSaving: viewModel.isSaving,
entity: client,
onSelected: viewModel.onActionSelected,
onSelected: viewModel.onEntityAction,
entityActions: viewModel.client.getActions(user: user),
)
],

View File

@ -145,8 +145,8 @@ class ClientOverview extends StatelessWidget {
title: localization.expenses,
onTap: () =>
viewModel.onEntityPressed(context, EntityType.expense),
onLongPress: () =>
viewModel.onEntityPressed(context, EntityType.expense, true),
onLongPress: () => viewModel.onEntityPressed(
context, EntityType.expense, true),
subtitle: memoizedExpenseStatsForClient(
client.id,
state.expenseState.map,

View File

@ -44,7 +44,7 @@ class ClientViewVM {
ClientViewVM({
@required this.client,
@required this.company,
@required this.onActionSelected,
@required this.onEntityAction,
@required this.onEntityPressed,
@required this.onEditPressed,
@required this.onBackPressed,
@ -173,14 +173,14 @@ class ClientViewVM {
store.dispatch(UpdateCurrentRoute(ClientScreen.route));
}
},
onActionSelected: (BuildContext context, EntityAction action) =>
onEntityAction: (BuildContext context, EntityAction action) =>
handleClientAction(context, client, action),
);
}
final ClientEntity client;
final CompanyEntity company;
final Function(BuildContext, EntityAction) onActionSelected;
final Function(BuildContext, EntityAction) onEntityAction;
final Function(BuildContext) onEditPressed;
final Function onBackPressed;
final Function(BuildContext, EntityType, [bool]) onEntityPressed;

View File

@ -20,8 +20,7 @@ class ExpenseEditDocumentsState extends State<ExpenseEditDocuments> {
@override
void didChangeDependencies() {
final List<TextEditingController> _controllers = [
];
final List<TextEditingController> _controllers = [];
_controllers
.forEach((dynamic controller) => controller.removeListener(_onChanged));
@ -64,15 +63,14 @@ class ExpenseEditDocumentsState extends State<ExpenseEditDocuments> {
FormCard(
children: <Widget>[
SwitchListTile(
activeColor: Theme.of(context).accentColor,
title: Text(localization.addDocumentsToInvoice),
value: expense.invoiceDocuments,
onChanged: (value) {
viewModel.onChanged(
expense.rebuild((b) => b..invoiceDocuments = value));
viewModel.onAddDocumentsChanged(value);
}
)
activeColor: Theme.of(context).accentColor,
title: Text(localization.addDocumentsToInvoice),
value: expense.invoiceDocuments,
onChanged: (value) {
viewModel.onChanged(
expense.rebuild((b) => b..invoiceDocuments = value));
viewModel.onAddDocumentsChanged(value);
})
],
),
],

View File

@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
@ -17,41 +18,6 @@ class ExpenseList extends StatelessWidget {
final ExpenseListVM viewModel;
void _showMenu(BuildContext context, ExpenseEntity expense) async {
if (expense == null) {
return;
}
final user = viewModel.user;
final message = await showDialog<String>(
context: context,
builder: (BuildContext dialogContext) => SimpleDialog(
children: expense
.getActions(user: user, includeEdit: true)
.map((entityAction) {
if (entityAction == null) {
return Divider();
} else {
return ListTile(
leading: Icon(getEntityActionIcon(entityAction)),
title: Text(AppLocalization.of(context)
.lookup(entityAction.toString())),
onTap: () {
Navigator.of(dialogContext).pop();
viewModel.onEntityAction(context, expense, entityAction);
},
);
}
}).toList()));
if (message != null) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: message,
)));
}
}
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
@ -119,12 +85,21 @@ class ExpenseList extends StatelessWidget {
separatorBuilder: (context, index) => ListDivider(),
itemCount: viewModel.expenseList.length,
itemBuilder: (BuildContext context, index) {
final user = viewModel.user;
final expenseId = viewModel.expenseList[index];
final expense = viewModel.expenseMap[expenseId];
final client =
viewModel.state.clientState.map[expense.clientId];
final vendor =
viewModel.state.vendorState.map[expense.vendorId];
void showDialog() => showEntityActionsDialog(
entity: expense,
context: context,
user: user,
client: client,
onEntityAction: viewModel.onEntityAction);
return ExpenseListItem(
user: viewModel.user,
filter: viewModel.filter,
@ -134,13 +109,13 @@ class ExpenseList extends StatelessWidget {
onTap: () => viewModel.onExpenseTap(context, expense),
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
_showMenu(context, expense);
showDialog();
} else {
viewModel.onEntityAction(
context, expense, action);
}
},
onLongPress: () => _showMenu(context, expense),
onLongPress: () => showDialog(),
);
},
),

View File

@ -1,5 +1,4 @@
import 'dart:async';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart';
import 'package:redux/redux.dart';
import 'package:flutter/material.dart';
@ -92,49 +91,9 @@ class ExpenseListVM {
onExpenseTap: (context, expense) {
store.dispatch(ViewExpense(expenseId: expense.id, context: context));
},
onEntityAction: (context, expense, action) {
switch (action) {
case EntityAction.edit:
store.dispatch(EditExpense(context: context, expense: expense));
break;
case EntityAction.clone:
store.dispatch(
EditExpense(context: context, expense: expense.clone));
break;
case EntityAction.newInvoice:
final item = convertExpenseToInvoiceItem(expense: expense);
store.dispatch(EditInvoice(
invoice: InvoiceEntity(company: state.selectedCompany)
.rebuild((b) => b
..hasExpenses = true
..clientId = expense.clientId
..invoiceItems.add(item)),
context: context));
break;
case EntityAction.viewInvoice:
store.dispatch(
ViewInvoice(invoiceId: expense.invoiceId, context: context));
break;
case EntityAction.restore:
store.dispatch(RestoreExpenseRequest(
snackBarCompleter(
context, AppLocalization.of(context).restoredExpense),
expense.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveExpenseRequest(
snackBarCompleter(
context, AppLocalization.of(context).archivedExpense),
expense.id));
break;
case EntityAction.delete:
store.dispatch(DeleteExpenseRequest(
snackBarCompleter(
context, AppLocalization.of(context).deletedExpense),
expense.id));
break;
}
},
onEntityAction:
(BuildContext context, BaseEntity expense, EntityAction action) =>
handleExpenseAction(context, expense, action),
onRefreshed: (context) => _handleRefresh(context),
);
}

View File

@ -157,7 +157,7 @@ class _CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
user: viewModel.company.user,
isSaving: viewModel.isSaving,
entity: expense,
onSelected: viewModel.onActionSelected,
onSelected: viewModel.onEntityAction,
entityActions: viewModel.expense.getActions(user: user),
)
],

View File

@ -1,6 +1,5 @@
import 'dart:async';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_selectors.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
@ -42,7 +41,7 @@ class ExpenseViewVM {
@required this.state,
@required this.expense,
@required this.company,
@required this.onActionSelected,
@required this.onEntityAction,
@required this.onEntityPressed,
@required this.onEditPressed,
@required this.onBackPressed,
@ -120,50 +119,17 @@ class ExpenseViewVM {
break;
}
},
onActionSelected: (BuildContext context, EntityAction action) {
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.clone:
store.dispatch(
EditExpense(context: context, expense: expense.clone));
break;
case EntityAction.newInvoice:
final item = convertExpenseToInvoiceItem(expense: expense);
store.dispatch(EditInvoice(
invoice: InvoiceEntity(company: state.selectedCompany)
.rebuild((b) => b
..hasExpenses = true
..clientId = expense.clientId
..invoiceItems.add(item)),
context: context));
break;
case EntityAction.viewInvoice:
store.dispatch(
ViewInvoice(invoiceId: expense.invoiceId, context: context));
break;
case EntityAction.archive:
store.dispatch(ArchiveExpenseRequest(
popCompleter(context, localization.archivedExpense),
expense.id));
break;
case EntityAction.delete:
store.dispatch(DeleteExpenseRequest(
popCompleter(context, localization.deletedExpense),
expense.id));
break;
case EntityAction.restore:
store.dispatch(RestoreExpenseRequest(
snackBarCompleter(context, localization.restoredExpense),
expense.id));
break;
}
});
onEntityAction: (BuildContext context, EntityAction action) =>
handleExpenseAction(context, expense, action),
);
}
final AppState state;
final ExpenseEntity expense;
final CompanyEntity company;
final Function(BuildContext, EntityAction) onActionSelected;
final Function(BuildContext, EntityAction) onEntityAction;
final Function(BuildContext, EntityType, [bool]) onEntityPressed;
final Function(BuildContext) onEditPressed;
final Function onBackPressed;

View File

@ -101,8 +101,8 @@ class _InvoiceItemSelectorState extends State<InvoiceItemSelector>
void _updateClientId() {
final selected = _selected.firstWhere(
(entity) =>
entity is BelongsToClient &&
(entity) =>
entity is BelongsToClient &&
(((entity as BelongsToClient).clientId ?? 0) > 0),
orElse: () => null);

View File

@ -1,13 +1,11 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/invoice_model.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
import 'package:invoiceninja_flutter/ui/invoice/invoice_list_item.dart';
import 'package:invoiceninja_flutter/ui/invoice/invoice_list_vm.dart';
import 'package:invoiceninja_flutter/utils/icons.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class InvoiceList extends StatelessWidget {
@ -18,43 +16,6 @@ class InvoiceList extends StatelessWidget {
final EntityListVM viewModel;
void _showMenu(
BuildContext context, InvoiceEntity invoice, ClientEntity client) async {
if (invoice == null || client == null) {
return;
}
final user = viewModel.user;
final message = await showDialog<String>(
context: context,
builder: (BuildContext dialogContext) => SimpleDialog(
children: invoice
.getActions(
user: user, client: client, includeEdit: true)
.map((entityAction) {
if (entityAction == null) {
return Divider();
} else {
return ListTile(
leading: Icon(getEntityActionIcon(entityAction)),
title: Text(AppLocalization.of(context)
.lookup(entityAction.toString())),
onTap: () {
Navigator.of(dialogContext).pop();
viewModel.onEntityAction(context, invoice, entityAction);
},
);
}
}).toList()));
if (message != null) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: message,
)));
}
}
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
@ -117,11 +78,20 @@ class InvoiceList extends StatelessWidget {
separatorBuilder: (context, index) => ListDivider(),
itemCount: viewModel.invoiceList.length,
itemBuilder: (BuildContext context, index) {
final user = viewModel.user;
final invoiceId = viewModel.invoiceList[index];
final invoice = viewModel.invoiceMap[invoiceId];
final client =
viewModel.clientMap[invoice.clientId] ??
ClientEntity();
void showDialog() => showEntityActionsDialog(
entity: invoice,
context: context,
user: user,
client: client,
onEntityAction: viewModel.onEntityAction);
return InvoiceListItem(
user: viewModel.user,
filter: viewModel.filter,
@ -132,14 +102,13 @@ class InvoiceList extends StatelessWidget {
viewModel.onInvoiceTap(context, invoice),
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
_showMenu(context, invoice, client);
showDialog();
} else {
viewModel.onEntityAction(
context, invoice, action);
}
},
onLongPress: () =>
_showMenu(context, invoice, client),
onLongPress: () => showDialog(),
);
},
),

View File

@ -6,18 +6,14 @@ import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_actions.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/pdf.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/invoice/invoice_list.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:url_launcher/url_launcher.dart';
class InvoiceListBuilder extends StatelessWidget {
const InvoiceListBuilder({Key key}) : super(key: key);
@ -136,63 +132,9 @@ class InvoiceListVM extends EntityListVM {
ViewClient(
clientId: state.invoiceListState.filterEntityId,
context: context)),
onEntityAction: (context, invoice, action) async {
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.edit:
store.dispatch(EditInvoice(context: context, invoice: invoice));
break;
case EntityAction.pdf:
viewPdf(invoice, context);
break;
case EntityAction.clientPortal:
if (await canLaunch(invoice.invitationSilentLink)) {
await launch(invoice.invitationSilentLink,
forceSafariVC: false, forceWebView: false);
}
break;
case EntityAction.markSent:
store.dispatch(MarkSentInvoiceRequest(
snackBarCompleter(context, localization.markedInvoiceAsSent),
invoice.id));
break;
case EntityAction.sendEmail:
store.dispatch(ShowEmailInvoice(
completer:
snackBarCompleter(context, localization.emailedInvoice),
invoice: invoice,
context: context));
break;
case EntityAction.cloneToInvoice:
store.dispatch(
EditInvoice(context: context, invoice: invoice.cloneToInvoice));
break;
case EntityAction.cloneToQuote:
store.dispatch(
EditQuote(context: context, quote: invoice.cloneToQuote));
break;
case EntityAction.enterPayment:
store.dispatch(EditPayment(
context: context,
payment: invoice.createPayment(state.selectedCompany)));
break;
case EntityAction.restore:
store.dispatch(RestoreInvoiceRequest(
snackBarCompleter(context, localization.restoredInvoice),
invoice.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveInvoiceRequest(
snackBarCompleter(context, localization.archivedInvoice),
invoice.id));
break;
case EntityAction.delete:
store.dispatch(DeleteInvoiceRequest(
snackBarCompleter(context, localization.deletedInvoice),
invoice.id));
break;
}
},
onEntityAction:
(BuildContext context, BaseEntity invoice, EntityAction action) =>
handleInvoiceAction(context, invoice, action),
);
}
}

View File

@ -309,8 +309,7 @@ class _CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
: Container(),
ActionMenuButton(
user: user,
entityActions:
invoice.getActions(client: client, user: user),
entityActions: invoice.getActions(client: client, user: user),
isSaving: viewModel.isSaving,
entity: invoice,
onSelected: viewModel.onActionSelected,

View File

@ -1,6 +1,5 @@
import 'dart:async';
import 'package:invoiceninja_flutter/redux/payment/payment_actions.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:redux/redux.dart';
import 'package:flutter/foundation.dart';
@ -11,13 +10,11 @@ import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/invoice/invoice_screen.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/pdf.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
import 'package:url_launcher/url_launcher.dart';
class InvoiceViewScreen extends StatelessWidget {
const InvoiceViewScreen({Key key}) : super(key: key);
@ -93,7 +90,7 @@ class InvoiceViewVM extends EntityViewVM {
ClientEntity client,
bool isSaving,
bool isDirty,
Function(BuildContext, EntityAction) onActionSelected,
Function(BuildContext, EntityAction) onEntityAction,
Function(BuildContext, [InvoiceItemEntity]) onEditPressed,
Function(BuildContext, [bool]) onClientPressed,
Function(BuildContext, PaymentEntity, [bool]) onPaymentPressed,
@ -106,7 +103,7 @@ class InvoiceViewVM extends EntityViewVM {
client: client,
isSaving: isSaving,
isDirty: isDirty,
onActionSelected: onActionSelected,
onActionSelected: onEntityAction,
onEditPressed: onEditPressed,
onClientPressed: onClientPressed,
onPaymentPressed: onPaymentPressed,
@ -130,110 +127,57 @@ class InvoiceViewVM extends EntityViewVM {
}
return InvoiceViewVM(
company: state.selectedCompany,
isSaving: state.isSaving,
isDirty: invoice.isNew,
invoice: invoice,
client: client,
onEditPressed: (BuildContext context, [InvoiceItemEntity invoiceItem]) {
final Completer<InvoiceEntity> completer =
new Completer<InvoiceEntity>();
store.dispatch(EditInvoice(
invoice: invoice,
context: context,
completer: completer,
invoiceItem: invoiceItem));
completer.future.then((invoice) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: AppLocalization.of(context).updatedInvoice,
)));
});
},
onRefreshed: (context) => _handleRefresh(context),
onBackPressed: () {
if (state.uiState.currentRoute.contains(InvoiceScreen.route)) {
store.dispatch(UpdateCurrentRoute(InvoiceScreen.route));
}
},
onClientPressed: (BuildContext context, [bool longPress = false]) {
if (longPress) {
showEntityActionsDialog(
user: state.selectedCompany.user,
context: context,
entity: client,
onEntityAction: (BuildContext context, BaseEntity client,
EntityAction action) =>
handleClientAction(context, client, action));
} else {
store.dispatch(ViewClient(clientId: client.id, context: context));
}
},
onPaymentPressed: (BuildContext context, PaymentEntity payment,
[bool longPress = false]) =>
store.dispatch(longPress
? EditPayment(payment: payment, context: context)
: ViewPayment(paymentId: payment.id, context: context)),
onPaymentsPressed: (BuildContext context) {
store.dispatch(FilterPaymentsByEntity(
entityId: invoice.id, entityType: EntityType.invoice));
store.dispatch(ViewPaymentList(context));
},
onActionSelected: (BuildContext context, EntityAction action) async {
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.pdf:
viewPdf(invoice, context);
break;
case EntityAction.clientPortal:
if (await canLaunch(invoice.invitationSilentLink)) {
await launch(invoice.invitationSilentLink,
forceSafariVC: false, forceWebView: false);
}
break;
case EntityAction.markSent:
store.dispatch(MarkSentInvoiceRequest(
snackBarCompleter(context, localization.markedInvoiceAsSent),
invoice.id));
break;
case EntityAction.sendEmail:
store.dispatch(ShowEmailInvoice(
completer:
snackBarCompleter(context, localization.emailedInvoice),
invoice: invoice,
context: context));
break;
case EntityAction.archive:
store.dispatch(ArchiveInvoiceRequest(
popCompleter(context, localization.archivedInvoice),
invoice.id));
break;
case EntityAction.delete:
store.dispatch(DeleteInvoiceRequest(
popCompleter(context, localization.deletedInvoice),
invoice.id));
break;
case EntityAction.restore:
store.dispatch(RestoreInvoiceRequest(
snackBarCompleter(context, localization.restoredInvoice),
invoice.id));
break;
case EntityAction.cloneToInvoice:
Navigator.of(context).pop();
store.dispatch(EditInvoice(
context: context, invoice: invoice.cloneToInvoice));
break;
case EntityAction.cloneToQuote:
Navigator.of(context).pop();
store.dispatch(
EditQuote(context: context, quote: invoice.cloneToQuote));
break;
case EntityAction.enterPayment:
store.dispatch(EditPayment(
context: context,
payment: invoice.createPayment(state.selectedCompany)));
break;
}
company: state.selectedCompany,
isSaving: state.isSaving,
isDirty: invoice.isNew,
invoice: invoice,
client: client,
onEditPressed: (BuildContext context, [InvoiceItemEntity invoiceItem]) {
final Completer<InvoiceEntity> completer =
new Completer<InvoiceEntity>();
store.dispatch(EditInvoice(
invoice: invoice,
context: context,
completer: completer,
invoiceItem: invoiceItem));
completer.future.then((invoice) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: AppLocalization.of(context).updatedInvoice,
)));
});
},
onRefreshed: (context) => _handleRefresh(context),
onBackPressed: () {
if (state.uiState.currentRoute.contains(InvoiceScreen.route)) {
store.dispatch(UpdateCurrentRoute(InvoiceScreen.route));
}
},
onClientPressed: (BuildContext context, [bool longPress = false]) {
if (longPress) {
showEntityActionsDialog(
user: state.selectedCompany.user,
context: context,
entity: client,
onEntityAction: (BuildContext context, BaseEntity client,
EntityAction action) =>
handleClientAction(context, client, action));
} else {
store.dispatch(ViewClient(clientId: client.id, context: context));
}
},
onPaymentPressed: (BuildContext context, PaymentEntity payment,
[bool longPress = false]) =>
store.dispatch(longPress
? EditPayment(payment: payment, context: context)
: ViewPayment(paymentId: payment.id, context: context)),
onPaymentsPressed: (BuildContext context) {
store.dispatch(FilterPaymentsByEntity(
entityId: invoice.id, entityType: EntityType.invoice));
store.dispatch(ViewPaymentList(context));
},
onEntityAction: (BuildContext context, EntityAction action) =>
handleInvoiceAction(context, invoice, action),
);
}
}

View File

@ -1,16 +1,14 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/payment_model.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_selectors.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
import 'package:invoiceninja_flutter/ui/payment/payment_list_item.dart';
import 'package:invoiceninja_flutter/ui/payment/payment_list_vm.dart';
import 'package:invoiceninja_flutter/utils/icons.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class PaymentList extends StatelessWidget {
@ -21,43 +19,6 @@ class PaymentList extends StatelessWidget {
final PaymentListVM viewModel;
void _showMenu(
BuildContext context, PaymentEntity payment, ClientEntity client) async {
if (payment == null || client == null) {
return;
}
final user = viewModel.user;
final message = await showDialog<String>(
context: context,
builder: (BuildContext dialogContext) => SimpleDialog(
children: payment
.getActions(
user: user, client: client, includeEdit: true)
.map((entityAction) {
if (entityAction == null) {
return Divider();
} else {
return ListTile(
leading: Icon(getEntityActionIcon(entityAction)),
title: Text(AppLocalization.of(context)
.lookup(entityAction.toString())),
onTap: () {
Navigator.of(dialogContext).pop();
viewModel.onEntityAction(context, payment, entityAction);
},
);
}
}).toList()));
if (message != null) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: message,
)));
}
}
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
@ -134,10 +95,19 @@ class PaymentList extends StatelessWidget {
separatorBuilder: (context, index) => ListDivider(),
itemCount: viewModel.paymentList.length,
itemBuilder: (BuildContext context, index) {
final user = viewModel.user;
final paymentId = viewModel.paymentList[index];
final payment = state.paymentState.map[paymentId];
final client =
paymentClientSelector(paymentId, state);
void showDialog() => showEntityActionsDialog(
entity: payment,
context: context,
user: user,
client: client,
onEntityAction: viewModel.onEntityAction);
return PaymentListItem(
user: viewModel.user,
filter: viewModel.filter,
@ -146,14 +116,13 @@ class PaymentList extends StatelessWidget {
viewModel.onPaymentTap(context, payment),
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
_showMenu(context, payment, client);
showDialog();
} else {
viewModel.onEntityAction(
context, payment, action);
}
},
onLongPress: () =>
_showMenu(context, payment, client),
onLongPress: () => showDialog(),
);
},
),

View File

@ -79,34 +79,9 @@ class PaymentListVM {
onPaymentTap: (context, payment) {
store.dispatch(ViewPayment(paymentId: payment.id, context: context));
},
onEntityAction: (context, payment, action) {
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.edit:
store.dispatch(EditPayment(context: context, payment: payment));
break;
case EntityAction.sendEmail:
store.dispatch(EmailPaymentRequest(
snackBarCompleter(context, localization.emailedPayment),
payment));
break;
case EntityAction.restore:
store.dispatch(RestorePaymentRequest(
snackBarCompleter(context, localization.restoredPayment),
payment.id));
break;
case EntityAction.archive:
store.dispatch(ArchivePaymentRequest(
snackBarCompleter(context, localization.archivedPayment),
payment.id));
break;
case EntityAction.delete:
store.dispatch(DeletePaymentRequest(
snackBarCompleter(context, localization.deletedPayment),
payment.id));
break;
}
},
onEntityAction:
(BuildContext context, BaseEntity payment, EntityAction action) =>
handlePaymentAction(context, payment, action),
onClearEntityFilterPressed: () =>
store.dispatch(FilterPaymentsByEntity()),
onViewClientFilterPressed: (BuildContext context) {

View File

@ -70,7 +70,7 @@ class _PaymentViewState extends State<PaymentView> {
user: viewModel.company.user,
isSaving: viewModel.isSaving,
entity: payment,
onSelected: viewModel.onActionSelected,
onSelected: viewModel.onEntityAction,
entityActions: viewModel.payment.getActions(
user: viewModel.company.user, client: client),
)

View File

@ -3,8 +3,6 @@ import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/payment/payment_selectors.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/payment/payment_actions.dart';
@ -37,7 +35,7 @@ class PaymentViewVM {
PaymentViewVM({
@required this.payment,
@required this.company,
@required this.onActionSelected,
@required this.onEntityAction,
@required this.onEditPressed,
@required this.onClientPressed,
@required this.onInvoicePressed,
@ -53,53 +51,32 @@ class PaymentViewVM {
final client = paymentClientSelector(payment.id, state) ?? ClientEntity();
return PaymentViewVM(
company: state.selectedCompany,
isSaving: state.isSaving,
isDirty: payment.isNew,
isLoading: state.isLoading,
payment: payment,
onEditPressed: (BuildContext context) {
store.dispatch(EditPayment(payment: payment, context: context));
},
onClientPressed: (context, [bool longPress = false]) => store.dispatch(
longPress
? EditClient(client: client, context: context)
: ViewClient(clientId: client.id, context: context)),
onInvoicePressed: (context, [bool longPress = false]) => store.dispatch(
longPress
? EditInvoice(
invoice: state.invoiceState.map[payment.invoiceId],
context: context)
: ViewInvoice(invoiceId: payment.invoiceId, context: context)),
onActionSelected: (BuildContext context, EntityAction action) {
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.sendEmail:
store.dispatch(EmailPaymentRequest(
popCompleter(context, localization.emailedPayment), payment));
break;
case EntityAction.archive:
store.dispatch(ArchivePaymentRequest(
popCompleter(context, localization.archivedClient),
payment.id));
break;
case EntityAction.delete:
store.dispatch(DeletePaymentRequest(
popCompleter(context, localization.deletedClient),
payment.id));
break;
case EntityAction.restore:
store.dispatch(RestorePaymentRequest(
snackBarCompleter(context, localization.restoredClient),
payment.id));
break;
}
});
company: state.selectedCompany,
isSaving: state.isSaving,
isDirty: payment.isNew,
isLoading: state.isLoading,
payment: payment,
onEditPressed: (BuildContext context) {
store.dispatch(EditPayment(payment: payment, context: context));
},
onClientPressed: (context, [bool longPress = false]) => store.dispatch(
longPress
? EditClient(client: client, context: context)
: ViewClient(clientId: client.id, context: context)),
onInvoicePressed: (context, [bool longPress = false]) => store.dispatch(
longPress
? EditInvoice(
invoice: state.invoiceState.map[payment.invoiceId],
context: context)
: ViewInvoice(invoiceId: payment.invoiceId, context: context)),
onEntityAction: (BuildContext context, EntityAction action) =>
handlePaymentAction(context, payment, action),
);
}
final PaymentEntity payment;
final CompanyEntity company;
final Function(BuildContext, EntityAction) onActionSelected;
final Function(BuildContext, EntityAction) onEntityAction;
final Function(BuildContext) onEditPressed;
final Function(BuildContext, [bool]) onInvoicePressed;
final Function(BuildContext, [bool]) onClientPressed;

View File

@ -71,8 +71,18 @@ class ProductEditVM {
: AppLocalization.of(context).updatedProduct),
product: product));
},
onEntityAction: (BuildContext context, EntityAction action) =>
handleProductAction(context, product, action),
onEntityAction: (BuildContext context, EntityAction action) {
// TODO Add view page for products
// Prevent duplicate global key error
if (action == EntityAction.clone) {
Navigator.pop(context);
WidgetsBinding.instance.addPostFrameCallback((duration) {
handleProductAction(context, product, action);
});
} else {
handleProductAction(context, product, action);
}
},
);
}

View File

@ -46,27 +46,26 @@ class ProductList extends StatelessWidget {
itemBuilder: (BuildContext context, index) {
final productId = viewModel.productList[index];
final product = viewModel.productMap[productId];
void showDialog() => showEntityActionsDialog(
entity: product,
context: context,
user: viewModel.user,
onEntityAction: viewModel.onEntityAction);
return ProductListItem(
user: viewModel.user,
filter: viewModel.filter,
product: product,
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
showEntityActionsDialog(
entity: product,
context: context,
user: viewModel.user,
onEntityAction: viewModel.onEntityAction);
showDialog();
} else {
viewModel.onEntityAction(context, product, action);
}
},
onTap: () => viewModel.onProductTap(context, product),
onLongPress: () => showEntityActionsDialog(
entity: product,
context: context,
user: viewModel.user,
onEntityAction: viewModel.onEntityAction),
onLongPress: () => showDialog(),
);
}),
);

View File

@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
@ -17,43 +18,6 @@ class ProjectList extends StatelessWidget {
final ProjectListVM viewModel;
void _showMenu(
BuildContext context, ProjectEntity project, ClientEntity client) async {
if (project == null || client == null) {
return;
}
final user = viewModel.user;
final message = await showDialog<String>(
context: context,
builder: (BuildContext dialogContext) => SimpleDialog(
children: project
.getActions(
user: user, client: client, includeEdit: true)
.map((entityAction) {
if (entityAction == null) {
return Divider();
} else {
return ListTile(
leading: Icon(getEntityActionIcon(entityAction)),
title: Text(AppLocalization.of(context)
.lookup(entityAction.toString())),
onTap: () {
Navigator.of(dialogContext).pop();
viewModel.onEntityAction(context, project, entityAction);
},
);
}
}).toList()));
if (message != null) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: message,
)));
}
}
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
@ -116,30 +80,37 @@ class ProjectList extends StatelessWidget {
separatorBuilder: (context, index) => ListDivider(),
itemCount: viewModel.projectList.length,
itemBuilder: (BuildContext context, index) {
final user = viewModel.user;
final projectId = viewModel.projectList[index];
final project = viewModel.projectMap[projectId];
final client =
viewModel.clientMap[project.clientId] ??
ClientEntity(id: project.clientId);
void showDialog() => showEntityActionsDialog(
entity: project,
context: context,
user: user,
client: client,
onEntityAction: viewModel.onEntityAction);
return ProjectListItem(
user: viewModel.user,
filter: viewModel.filter,
project: project,
client:
viewModel.clientMap[project.clientId] ??
client: viewModel.clientMap[project.clientId] ??
ClientEntity(),
onTap: () =>
viewModel.onProjectTap(context, project),
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
_showMenu(context, project, client);
showDialog();
} else {
viewModel.onEntityAction(
context, project, action);
}
},
onLongPress: () =>
_showMenu(context, project, client),
onLongPress: () => showDialog(),
);
},
),

View File

@ -1,6 +1,5 @@
import 'dart:async';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
import 'package:redux/redux.dart';
import 'package:flutter/material.dart';
@ -84,46 +83,9 @@ class ProjectListVM {
onProjectTap: (context, project) {
store.dispatch(ViewProject(projectId: project.id, context: context));
},
onEntityAction: (context, project, action) {
switch (action) {
case EntityAction.edit:
store.dispatch(EditProject(context: context, project: project));
break;
case EntityAction.newInvoice:
final items =
convertProjectToInvoiceItem(project: project, context: context);
store.dispatch(EditInvoice(
invoice: InvoiceEntity(company: state.selectedCompany)
.rebuild((b) => b
..hasTasks = true
..clientId = project.clientId
..invoiceItems.addAll(items)),
context: context));
break;
case EntityAction.clone:
store.dispatch(
EditProject(context: context, project: project.clone));
break;
case EntityAction.restore:
store.dispatch(RestoreProjectRequest(
snackBarCompleter(
context, AppLocalization.of(context).restoredProject),
project.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveProjectRequest(
snackBarCompleter(
context, AppLocalization.of(context).archivedProject),
project.id));
break;
case EntityAction.delete:
store.dispatch(DeleteProjectRequest(
snackBarCompleter(
context, AppLocalization.of(context).deletedProject),
project.id));
break;
}
},
onEntityAction:
(BuildContext context, BaseEntity project, EntityAction action) =>
handleProjectAction(context, project, action),
onRefreshed: (context) => _handleRefresh(context),
);
}

View File

@ -187,11 +187,11 @@ class _CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
: Container(),
ActionMenuButton(
user: user,
entityActions: project.getActions(
client: viewModel.client, user: user),
entityActions:
project.getActions(client: viewModel.client, user: user),
isSaving: viewModel.isSaving,
entity: project,
onSelected: viewModel.onActionSelected,
onSelected: viewModel.onEntityAction,
)
],
);

View File

@ -1,8 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/project/project_selectors.dart';
import 'package:invoiceninja_flutter/redux/task/task_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/project/project_screen.dart';
@ -41,7 +39,7 @@ class ProjectViewVM {
@required this.project,
@required this.client,
@required this.company,
@required this.onActionSelected,
@required this.onEntityAction,
@required this.onTasksPressed,
@required this.onEditPressed,
@required this.onBackPressed,
@ -99,48 +97,17 @@ class ProjectViewVM {
store.dispatch(UpdateCurrentRoute(ProjectScreen.route));
}
},
onActionSelected: (BuildContext context, EntityAction action) {
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.newInvoice:
final items = convertProjectToInvoiceItem(
project: project, context: context);
store.dispatch(EditInvoice(
invoice: InvoiceEntity(company: state.selectedCompany)
.rebuild((b) => b
..hasTasks = true
..clientId = project.clientId
..invoiceItems.addAll(items)),
context: context));
break;
case EntityAction.clone:
store.dispatch(
EditProject(context: context, project: project.clone));
break;
case EntityAction.archive:
store.dispatch(ArchiveProjectRequest(
popCompleter(context, localization.archivedProject),
project.id));
break;
case EntityAction.delete:
store.dispatch(DeleteProjectRequest(
popCompleter(context, localization.deletedProject),
project.id));
break;
case EntityAction.restore:
store.dispatch(RestoreProjectRequest(
snackBarCompleter(context, localization.restoredProject),
project.id));
break;
}
});
onEntityAction: (BuildContext context, EntityAction action) =>
handleProjectAction(context, project, action),
);
}
final AppState state;
final ProjectEntity project;
final ClientEntity client;
final CompanyEntity company;
final Function(BuildContext, EntityAction) onActionSelected;
final Function(BuildContext, EntityAction) onEntityAction;
final Function(BuildContext) onEditPressed;
final Function(BuildContext, [bool]) onClientPressed;
final Function onBackPressed;

View File

@ -1,7 +1,6 @@
import 'dart:async';
import 'package:built_collection/built_collection.dart';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_selectors.dart';
import 'package:flutter/material.dart';
@ -13,11 +12,9 @@ import 'package:invoiceninja_flutter/ui/invoice/invoice_list.dart';
import 'package:invoiceninja_flutter/ui/invoice/invoice_list_vm.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/pdf.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:url_launcher/url_launcher.dart';
class QuoteListBuilder extends StatelessWidget {
const QuoteListBuilder({Key key}) : super(key: key);
@ -99,71 +96,9 @@ class QuoteListVM extends EntityListVM {
onViewEntityFilterPressed: (BuildContext context) => store.dispatch(
ViewClient(
clientId: state.quoteListState.filterEntityId, context: context)),
onEntityAction: (context, quote, action) async {
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.edit:
store.dispatch(EditQuote(context: context, quote: quote));
break;
case EntityAction.pdf:
viewPdf(quote, context);
break;
case EntityAction.clientPortal:
if (await canLaunch(quote.invitationSilentLink)) {
await launch(quote.invitationSilentLink,
forceSafariVC: false, forceWebView: false);
}
break;
case EntityAction.viewInvoice:
store.dispatch(
ViewInvoice(context: context, invoiceId: quote.quoteInvoiceId));
break;
case EntityAction.convert:
final Completer<InvoiceEntity> completer =
Completer<InvoiceEntity>();
store.dispatch(ConvertQuote(completer, quote.id));
completer.future.then((InvoiceEntity invoice) {
store.dispatch(
ViewInvoice(invoiceId: invoice.id, context: context));
});
break;
case EntityAction.markSent:
store.dispatch(MarkSentQuoteRequest(
snackBarCompleter(context, localization.markedQuoteAsSent),
quote.id));
break;
case EntityAction.sendEmail:
store.dispatch(ShowEmailQuote(
completer:
snackBarCompleter(context, localization.emailedQuote),
quote: quote,
context: context));
break;
case EntityAction.cloneToInvoice:
store.dispatch(
EditInvoice(context: context, invoice: quote.cloneToInvoice));
break;
case EntityAction.cloneToQuote:
store.dispatch(
EditQuote(context: context, quote: quote.cloneToQuote));
break;
case EntityAction.restore:
store.dispatch(RestoreQuoteRequest(
snackBarCompleter(context, localization.restoredQuote),
quote.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveQuoteRequest(
snackBarCompleter(context, localization.archivedQuote),
quote.id));
break;
case EntityAction.delete:
store.dispatch(DeleteQuoteRequest(
snackBarCompleter(context, localization.deletedQuote),
quote.id));
break;
}
},
onEntityAction:
(BuildContext context, BaseEntity quote, EntityAction action) =>
handleQuoteAction(context, quote, action),
);
}
}

View File

@ -3,20 +3,17 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart';
import 'package:invoiceninja_flutter/ui/quote/quote_screen.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/utils/pdf.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_actions.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
import 'package:url_launcher/url_launcher.dart';
class QuoteViewScreen extends StatelessWidget {
const QuoteViewScreen({Key key}) : super(key: key);
@ -46,7 +43,7 @@ class QuoteViewVM extends EntityViewVM {
ClientEntity client,
bool isSaving,
bool isDirty,
Function(BuildContext, EntityAction) onActionSelected,
Function(BuildContext, EntityAction) onEntityAction,
Function(BuildContext, [InvoiceItemEntity]) onEditPressed,
Function(BuildContext, [bool]) onClientPressed,
Function(BuildContext) onPaymentsPressed,
@ -59,7 +56,7 @@ class QuoteViewVM extends EntityViewVM {
client: client,
isSaving: isSaving,
isDirty: isDirty,
onActionSelected: onActionSelected,
onActionSelected: onEntityAction,
onEditPressed: onEditPressed,
onClientPressed: onClientPressed,
onPaymentsPressed: onPaymentsPressed,
@ -113,67 +110,9 @@ class QuoteViewVM extends EntityViewVM {
store.dispatch(longPress
? EditClient(client: client, context: context)
: ViewClient(clientId: client.id, context: context)),
onActionSelected: (BuildContext context, EntityAction action) async {
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.pdf:
viewPdf(quote, context);
break;
case EntityAction.clientPortal:
if (await canLaunch(quote.invitationSilentLink)) {
await launch(quote.invitationSilentLink,
forceSafariVC: false, forceWebView: false);
}
break;
case EntityAction.viewInvoice:
store.dispatch(ViewInvoice(
context: context, invoiceId: quote.quoteInvoiceId));
break;
case EntityAction.convert:
final Completer<InvoiceEntity> completer =
Completer<InvoiceEntity>();
store.dispatch(ConvertQuote(completer, quote.id));
completer.future.then((InvoiceEntity invoice) {
store.dispatch(
ViewInvoice(invoiceId: invoice.id, context: context));
});
break;
case EntityAction.markSent:
store.dispatch(MarkSentQuoteRequest(
snackBarCompleter(context, localization.markedQuoteAsSent),
quote.id));
break;
case EntityAction.sendEmail:
store.dispatch(ShowEmailQuote(
completer:
snackBarCompleter(context, localization.emailedQuote),
quote: quote,
context: context));
break;
case EntityAction.archive:
store.dispatch(ArchiveQuoteRequest(
popCompleter(context, localization.archivedQuote), quote.id));
break;
case EntityAction.delete:
store.dispatch(DeleteQuoteRequest(
popCompleter(context, localization.deletedQuote), quote.id));
break;
case EntityAction.restore:
store.dispatch(RestoreQuoteRequest(
snackBarCompleter(context, localization.restoredQuote),
quote.id));
break;
case EntityAction.cloneToInvoice:
Navigator.of(context).pop();
store.dispatch(
EditInvoice(context: context, invoice: quote.cloneToInvoice));
break;
case EntityAction.cloneToQuote:
Navigator.of(context).pop();
store.dispatch(
EditQuote(context: context, quote: quote.cloneToQuote));
break;
}
});
onEntityAction: (BuildContext context, EntityAction action) =>
handleQuoteAction(context, quote, action),
);
}
}

View File

@ -1,12 +1,11 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
import 'package:invoiceninja_flutter/ui/task/task_list_item.dart';
import 'package:invoiceninja_flutter/ui/task/task_list_vm.dart';
import 'package:invoiceninja_flutter/utils/icons.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class TaskList extends StatelessWidget {
@ -17,43 +16,6 @@ class TaskList extends StatelessWidget {
final TaskListVM viewModel;
void _showMenu(
BuildContext context, TaskEntity task, ClientEntity client) async {
if (task == null || client == null) {
return;
}
final user = viewModel.user;
final message = await showDialog<String>(
context: context,
builder: (BuildContext dialogContext) => SimpleDialog(
children: task
.getActions(
user: user, client: client, includeEdit: true)
.map((entityAction) {
if (entityAction == null) {
return Divider();
} else {
return ListTile(
leading: Icon(getEntityActionIcon(entityAction)),
title: Text(AppLocalization.of(context)
.lookup(entityAction.toString())),
onTap: () {
Navigator.of(dialogContext).pop();
viewModel.onEntityAction(context, task, entityAction);
},
);
}
}).toList()));
if (message != null) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: message,
)));
}
}
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
@ -127,10 +89,19 @@ class TaskList extends StatelessWidget {
separatorBuilder: (context, index) => ListDivider(),
itemCount: viewModel.taskList.length,
itemBuilder: (BuildContext context, index) {
final user = viewModel.user;
final taskId = viewModel.taskList[index];
final task = viewModel.taskMap[taskId];
final client = viewModel.clientMap[task.clientId] ??
ClientEntity();
void showDialog() => showEntityActionsDialog(
entity: task,
context: context,
user: user,
client: client,
onEntityAction: viewModel.onEntityAction);
return TaskListItem(
user: viewModel.user,
filter: viewModel.filter,
@ -139,18 +110,16 @@ class TaskList extends StatelessWidget {
ClientEntity(),
project: viewModel
.state.projectState.map[task.projectId],
onTap: () =>
viewModel.onTaskTap(context, task),
onTap: () => viewModel.onTaskTap(context, task),
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
_showMenu(context, task, client);
showDialog();
} else {
viewModel.onEntityAction(
context, task, action);
}
},
onLongPress: () =>
_showMenu(context, task, client),
onLongPress: () => showDialog(),
);
},
),

View File

@ -1,8 +1,5 @@
import 'dart:async';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/project/project_actions.dart';
import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
import 'package:redux/redux.dart';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
@ -99,73 +96,9 @@ class TaskListVM {
onTaskTap: (context, task) {
store.dispatch(ViewTask(taskId: task.id, context: context));
},
onEntityAction: (context, task, action) {
switch (action) {
case EntityAction.edit:
store.dispatch(EditTask(context: context, task: task));
break;
case EntityAction.start:
case EntityAction.stop:
case EntityAction.resume:
final Completer<TaskEntity> completer = new Completer<TaskEntity>();
final localization = AppLocalization.of(context);
store.dispatch(
SaveTaskRequest(completer: completer, task: task.toggle()));
completer.future.then((savedTask) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: savedTask.isRunning
? (savedTask.duration > 0
? localization.resumedTask
: localization.startedTask)
: localization.stoppedTask,
)));
}).catchError((Object error) {
showDialog<ErrorDialog>(
context: context,
builder: (BuildContext context) {
return ErrorDialog(error);
});
});
break;
case EntityAction.newInvoice:
final item = convertTaskToInvoiceItem(task: task, context: context);
store.dispatch(EditInvoice(
invoice: InvoiceEntity(company: state.selectedCompany)
.rebuild((b) => b
..hasTasks = true
..clientId = task.clientId
..invoiceItems.add(item)),
context: context));
break;
case EntityAction.viewInvoice:
store.dispatch(
ViewInvoice(invoiceId: task.invoiceId, context: context));
break;
case EntityAction.clone:
store.dispatch(EditTask(context: context, task: task.clone));
break;
case EntityAction.restore:
store.dispatch(RestoreTaskRequest(
snackBarCompleter(
context, AppLocalization.of(context).restoredTask),
task.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveTaskRequest(
snackBarCompleter(
context, AppLocalization.of(context).archivedTask),
task.id));
break;
case EntityAction.delete:
store.dispatch(DeleteTaskRequest(
snackBarCompleter(
context, AppLocalization.of(context).deletedTask),
task.id));
break;
}
},
onEntityAction:
(BuildContext context, BaseEntity task, EntityAction action) =>
handleTaskAction(context, task, action),
onRefreshed: (context) => _handleRefresh(context),
);
}

View File

@ -250,7 +250,7 @@ class _CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
task.getActions(client: viewModel.client, user: user),
isSaving: viewModel.isSaving,
entity: task,
onSelected: viewModel.onActionSelected,
onSelected: viewModel.onEntityAction,
)
],
);

View File

@ -1,12 +1,10 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/redux/project/project_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_actions.dart';
import 'package:invoiceninja_flutter/redux/task/task_selectors.dart';
import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
@ -47,7 +45,7 @@ class TaskViewVM {
@required this.project,
@required this.company,
@required this.state,
@required this.onActionSelected,
@required this.onEntityAction,
@required this.onEditPressed,
@required this.onBackPressed,
@required this.onRefreshed,
@ -151,47 +149,11 @@ class TaskViewVM {
store.dispatch(UpdateCurrentRoute(TaskScreen.route));
}
},
onActionSelected: (BuildContext context, EntityAction action) {
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.resume:
case EntityAction.start:
case EntityAction.stop:
_toggleTask(context);
break;
case EntityAction.newInvoice:
final item =
convertTaskToInvoiceItem(task: task, context: context);
store.dispatch(EditInvoice(
invoice: InvoiceEntity(company: state.selectedCompany)
.rebuild((b) => b
..hasTasks = true
..clientId = task.clientId
..invoiceItems.add(item)),
context: context));
break;
case EntityAction.clone:
store.dispatch(EditTask(context: context, task: task.clone));
break;
case EntityAction.viewInvoice:
store.dispatch(
ViewInvoice(invoiceId: task.invoiceId, context: context));
break;
case EntityAction.archive:
store.dispatch(ArchiveTaskRequest(
popCompleter(context, localization.archivedTask), task.id));
break;
case EntityAction.delete:
store.dispatch(DeleteTaskRequest(
popCompleter(context, localization.deletedTask), task.id));
break;
case EntityAction.restore:
store.dispatch(RestoreTaskRequest(
snackBarCompleter(context, localization.restoredTask),
task.id));
break;
}
});
onEntityAction: (BuildContext context, EntityAction action) =>
handleTaskAction(context, task, action),
);
}
final AppState state;
@ -199,7 +161,7 @@ class TaskViewVM {
final ClientEntity client;
final ProjectEntity project;
final CompanyEntity company;
final Function(BuildContext, EntityAction) onActionSelected;
final Function(BuildContext, EntityAction) onEntityAction;
final Function(BuildContext, [TaskTime]) onEditPressed;
final Function onBackPressed;
final Function(BuildContext) onFabPressed;

View File

@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/snackbar_row.dart';
@ -17,41 +18,6 @@ class VendorList extends StatelessWidget {
final VendorListVM viewModel;
void _showMenu(BuildContext context, VendorEntity vendor) async {
if (vendor == null) {
return;
}
final user = viewModel.user;
final message = await showDialog<String>(
context: context,
builder: (BuildContext dialogContext) => SimpleDialog(
children: vendor
.getActions(user: user, includeEdit: true)
.map((entityAction) {
if (entityAction == null) {
return Divider();
} else {
return ListTile(
leading: Icon(getEntityActionIcon(entityAction)),
title: Text(AppLocalization.of(context)
.lookup(entityAction.toString())),
onTap: () {
Navigator.of(dialogContext).pop();
viewModel.onEntityAction(context, vendor, entityAction);
},
);
}
}).toList()));
if (message != null) {
Scaffold.of(context).showSnackBar(SnackBar(
content: SnackBarRow(
message: message,
)));
}
}
@override
Widget build(BuildContext context) {
return Column(
@ -78,8 +44,16 @@ class VendorList extends StatelessWidget {
separatorBuilder: (context, index) => ListDivider(),
itemCount: viewModel.vendorList.length,
itemBuilder: (BuildContext context, index) {
final user = viewModel.user;
final vendorId = viewModel.vendorList[index];
final vendor = viewModel.vendorMap[vendorId];
void showDialog() => showEntityActionsDialog(
entity: vendor,
context: context,
user: user,
onEntityAction: viewModel.onEntityAction);
return VendorListItem(
user: viewModel.user,
filter: viewModel.filter,
@ -88,13 +62,13 @@ class VendorList extends StatelessWidget {
viewModel.onVendorTap(context, vendor),
onEntityAction: (EntityAction action) {
if (action == EntityAction.more) {
_showMenu(context, vendor);
showDialog();
} else {
viewModel.onEntityAction(
context, vendor, action);
}
},
onLongPress: () => _showMenu(context, vendor),
onLongPress: () => showDialog(),
);
},
),

View File

@ -5,7 +5,6 @@ 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/expense/expense_actions.dart';
import 'package:invoiceninja_flutter/redux/client/client_actions.dart';
import 'package:invoiceninja_flutter/redux/ui/list_ui_state.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
@ -78,37 +77,9 @@ class VendorListVM {
onVendorTap: (context, vendor) {
store.dispatch(ViewVendor(vendorId: vendor.id, context: context));
},
onEntityAction: (context, vendor, action) {
switch (action) {
case EntityAction.edit:
store.dispatch(EditVendor(context: context, vendor: vendor));
break;
case EntityAction.newExpense:
store.dispatch(EditExpense(
expense: ExpenseEntity(
company: state.selectedCompany, vendor: vendor),
context: context));
break;
case EntityAction.restore:
store.dispatch(RestoreVendorRequest(
snackBarCompleter(
context, AppLocalization.of(context).restoredVendor),
vendor.id));
break;
case EntityAction.archive:
store.dispatch(ArchiveVendorRequest(
snackBarCompleter(
context, AppLocalization.of(context).archivedVendor),
vendor.id));
break;
case EntityAction.delete:
store.dispatch(DeleteVendorRequest(
snackBarCompleter(
context, AppLocalization.of(context).deletedVendor),
vendor.id));
break;
}
},
onEntityAction:
(BuildContext context, BaseEntity vendor, EntityAction action) =>
handleVendorAction(context, vendor, action),
onRefreshed: (context) => _handleRefresh(context),
);
}

View File

@ -147,7 +147,7 @@ class _CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
user: viewModel.company.user,
isSaving: viewModel.isSaving,
entity: vendor,
onSelected: viewModel.onActionSelected,
onSelected: viewModel.onEntityAction,
entityActions: viewModel.vendor.getActions(user: user),
)
],

View File

@ -40,7 +40,7 @@ class VendorViewVM {
@required this.vendor,
@required this.company,
@required this.onAddExpensePressed,
@required this.onActionSelected,
@required this.onEntityAction,
@required this.onEntityPressed,
@required this.onEditPressed,
@required this.onBackPressed,
@ -107,38 +107,16 @@ class VendorViewVM {
onAddExpensePressed: (context) => store.dispatch(EditExpense(
expense: ExpenseEntity(company: company, vendor: vendor),
context: context)),
onActionSelected: (BuildContext context, EntityAction action) {
final localization = AppLocalization.of(context);
switch (action) {
case EntityAction.newExpense:
store.dispatch(EditExpense(
expense: ExpenseEntity(
company: state.selectedCompany, vendor: vendor),
context: context));
break;
case EntityAction.archive:
store.dispatch(ArchiveVendorRequest(
popCompleter(context, localization.archivedVendor),
vendor.id));
break;
case EntityAction.delete:
store.dispatch(DeleteVendorRequest(
popCompleter(context, localization.deletedVendor),
vendor.id));
break;
case EntityAction.restore:
store.dispatch(RestoreVendorRequest(
snackBarCompleter(context, localization.restoredVendor),
vendor.id));
break;
}
});
onEntityAction: (BuildContext context, EntityAction action) =>
handleVendorAction(context, vendor, action),
);
}
final AppState state;
final VendorEntity vendor;
final CompanyEntity company;
final Function(BuildContext, EntityAction) onActionSelected;
final Function(BuildContext, EntityAction) onEntityAction;
final Function(BuildContext) onEditPressed;
final Function(BuildContext, EntityType, [bool]) onEntityPressed;
final Function onBackPressed;

File diff suppressed because it is too large Load Diff

View File

@ -31,4 +31,4 @@ class ProductKeys {
class SettingsKeys {
static const String drawer = 'settingsDrawer';
}
}

View File

@ -6,7 +6,7 @@ import 'package:invoiceninja_flutter/constants.dart';
import 'i18n.dart';
class AppLocalization extends LocaleCodeAware with LocalizationsProvider {
AppLocalization(this.locale): super(locale.toString());
AppLocalization(this.locale) : super(locale.toString());
final Locale locale;
@ -18,8 +18,6 @@ class AppLocalization extends LocaleCodeAware with LocalizationsProvider {
static AppLocalization of(BuildContext context) {
return Localizations.of<AppLocalization>(context, AppLocalization);
}
}
class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalization> {