Implemented Multiselect for Expense

This commit is contained in:
Gianfranco Gasbarri 2019-10-19 22:36:15 +01:00
parent 571cd910d3
commit c389866edb
11 changed files with 349 additions and 67 deletions

View File

@ -13,6 +13,7 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/auth/auth_middleware.dart';
import 'package:invoiceninja_flutter/redux/client/client_middleware.dart';
import 'package:invoiceninja_flutter/redux/company/company_selectors.dart';
import 'package:invoiceninja_flutter/redux/company_gateway/company_gateway_middleware.dart';
import 'package:invoiceninja_flutter/redux/dashboard/dashboard_middleware.dart';
import 'package:invoiceninja_flutter/redux/document/document_middleware.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_middleware.dart';
@ -24,6 +25,7 @@ import 'package:invoiceninja_flutter/redux/project/project_middleware.dart';
import 'package:invoiceninja_flutter/redux/quote/quote_middleware.dart';
import 'package:invoiceninja_flutter/redux/settings/settings_middleware.dart';
import 'package:invoiceninja_flutter/redux/task/task_middleware.dart';
import 'package:invoiceninja_flutter/redux/tax_rate/tax_rate_middleware.dart';
import 'package:invoiceninja_flutter/redux/ui/ui_state.dart';
import 'package:invoiceninja_flutter/redux/vendor/vendor_middleware.dart';
import 'package:invoiceninja_flutter/ui/app/app_builder.dart';
@ -31,7 +33,13 @@ import 'package:invoiceninja_flutter/ui/app/main_screen.dart';
import 'package:invoiceninja_flutter/ui/app/screen_imports.dart';
import 'package:invoiceninja_flutter/ui/auth/init_screen.dart';
import 'package:invoiceninja_flutter/ui/auth/login_vm.dart';
import 'package:invoiceninja_flutter/ui/company_gateway/company_gateway_screen.dart';
import 'package:invoiceninja_flutter/ui/company_gateway/company_gateway_screen_vm.dart';
import 'package:invoiceninja_flutter/ui/company_gateway/edit/company_gateway_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/company_gateway/view/company_gateway_view_vm.dart';
import 'package:invoiceninja_flutter/ui/dashboard/dashboard_vm.dart';
import 'package:invoiceninja_flutter/ui/document/document_screen_vm.dart';
import 'package:invoiceninja_flutter/ui/expense/expense_screen_vm.dart';
import 'package:invoiceninja_flutter/ui/group/edit/group_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/group/group_screen.dart';
import 'package:invoiceninja_flutter/ui/group/view/group_view_vm.dart';
@ -53,6 +61,9 @@ import 'package:invoiceninja_flutter/ui/settings/products_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/tax_rates_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/templates_and_reminders_vm.dart';
import 'package:invoiceninja_flutter/ui/settings/user_details_vm.dart';
import 'package:invoiceninja_flutter/ui/tax_rate/edit/tax_rate_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/tax_rate/tax_rate_screen.dart';
import 'package:invoiceninja_flutter/ui/tax_rate/view/tax_rate_view_vm.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:local_auth/local_auth.dart';
import 'package:redux/redux.dart';
@ -60,17 +71,6 @@ import 'package:redux_logging/redux_logging.dart';
import 'package:sentry/sentry.dart';
import 'package:shared_preferences/shared_preferences.dart';
// STARTER: import - do not remove comment
import 'package:invoiceninja_flutter/ui/tax_rate/tax_rate_screen.dart';
import 'package:invoiceninja_flutter/ui/tax_rate/edit/tax_rate_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/tax_rate/view/tax_rate_view_vm.dart';
import 'package:invoiceninja_flutter/redux/tax_rate/tax_rate_middleware.dart';
import 'package:invoiceninja_flutter/ui/company_gateway/company_gateway_screen.dart';
import 'package:invoiceninja_flutter/ui/company_gateway/edit/company_gateway_edit_vm.dart';
import 'package:invoiceninja_flutter/ui/company_gateway/view/company_gateway_view_vm.dart';
import 'package:invoiceninja_flutter/redux/company_gateway/company_gateway_middleware.dart';
void main({bool isTesting = false}) async {
final SentryClient _sentry = Config.SENTRY_DNS.isEmpty
? null
@ -306,10 +306,10 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
InvoiceViewScreen.route: (context) => InvoiceViewScreen(),
InvoiceEditScreen.route: (context) => InvoiceEditScreen(),
InvoiceEmailScreen.route: (context) => InvoiceEmailScreen(),
DocumentScreen.route: (context) => DocumentScreen(),
DocumentScreen.route: (context) => DocumentScreenBuilder(),
DocumentViewScreen.route: (context) => DocumentViewScreen(),
DocumentEditScreen.route: (context) => DocumentEditScreen(),
ExpenseScreen.route: (context) => ExpenseScreen(),
ExpenseScreen.route: (context) => ExpenseScreenBuilder(),
ExpenseViewScreen.route: (context) => ExpenseViewScreen(),
ExpenseEditScreen.route: (context) => ExpenseEditScreen(),
VendorScreen.route: (context) => VendorScreen(),
@ -336,7 +336,8 @@ class InvoiceNinjaAppState extends State<InvoiceNinjaApp> {
CompanyDetailsScreen.route: (context) => CompanyDetailsScreen(),
UserDetailsScreen.route: (context) => UserDetailsScreen(),
LocalizationScreen.route: (context) => LocalizationScreen(),
CompanyGatewayScreen.route: (context) => CompanyGatewayScreen(),
CompanyGatewayScreen.route: (context) =>
CompanyGatewayScreenBuilder(),
CompanyGatewayViewScreen.route: (context) =>
CompanyGatewayViewScreen(),
CompanyGatewayEditScreen.route: (context) =>

View File

@ -1,15 +1,16 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:built_collection/built_collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_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';
import 'package:invoiceninja_flutter/redux/invoice/invoice_actions.dart';
import 'package:invoiceninja_flutter/utils/completers.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class ViewExpenseList implements PersistUI {
ViewExpenseList({@required this.context, this.force = false});
@ -245,11 +246,22 @@ class FilterExpensesByEntity implements PersistUI {
}
void handleExpenseAction(
BuildContext context, ExpenseEntity expense, EntityAction action) {
BuildContext context, List<ExpenseEntity> expenses, EntityAction action) {
assert(
[
EntityAction.restore,
EntityAction.archive,
EntityAction.delete,
EntityAction.toggleMultiselect
].contains(action) ||
expenses.length == 1,
'Cannot perform this action on more than one expense');
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final CompanyEntity company = state.selectedCompany;
final localization = AppLocalization.of(context);
final expense = expenses.first;
switch (action) {
case EntityAction.edit:
@ -286,5 +298,50 @@ void handleExpenseAction(
store.dispatch(DeleteExpenseRequest(
snackBarCompleter(context, localization.deletedExpense), expense.id));
break;
case EntityAction.toggleMultiselect:
if (!store.state.expenseListState.isInMultiselect()) {
store.dispatch(StartExpenseMultiselect(context: context));
}
if (expenses.isEmpty) {
break;
}
for (final expense in expenses) {
if (!store.state.expenseListState.isSelected(expense)) {
store.dispatch(
AddToExpenseMultiselect(context: context, entity: expense));
} else {
store.dispatch(
RemoveFromExpenseMultiselect(context: context, entity: expense));
}
}
break;
}
}
class StartExpenseMultiselect {
StartExpenseMultiselect({@required this.context});
final BuildContext context;
}
class AddToExpenseMultiselect {
AddToExpenseMultiselect({@required this.context, @required this.entity});
final BuildContext context;
final BaseEntity entity;
}
class RemoveFromExpenseMultiselect {
RemoveFromExpenseMultiselect({@required this.context, @required this.entity});
final BuildContext context;
final BaseEntity entity;
}
class ClearExpenseMultiselect {
ClearExpenseMultiselect({@required this.context});
final BuildContext context;
}

View File

@ -52,6 +52,11 @@ final expenseListReducer = combineReducers<ListUIState>([
TypedReducer<ListUIState, FilterExpensesByCustom1>(_filterExpensesByCustom1),
TypedReducer<ListUIState, FilterExpensesByCustom2>(_filterExpensesByCustom2),
TypedReducer<ListUIState, FilterExpensesByEntity>(_filterExpensesByClient),
TypedReducer<ListUIState, StartExpenseMultiselect>(_startListMultiselect),
TypedReducer<ListUIState, AddToExpenseMultiselect>(_addToListMultiselect),
TypedReducer<ListUIState, RemoveFromExpenseMultiselect>(
_removeFromListMultiselect),
TypedReducer<ListUIState, ClearExpenseMultiselect>(_clearListMultiselect),
]);
ListUIState _filterExpensesByClient(
@ -116,6 +121,28 @@ ListUIState _sortExpenses(ListUIState expenseListState, SortExpenses action) {
..sortField = action.field);
}
ListUIState _startListMultiselect(
ListUIState expenseListState, StartExpenseMultiselect action) {
return expenseListState.rebuild((b) => b..selectedEntities = <BaseEntity>[]);
}
ListUIState _addToListMultiselect(
ListUIState expenseListState, AddToExpenseMultiselect action) {
return expenseListState
.rebuild((b) => b..selectedEntities.add(action.entity));
}
ListUIState _removeFromListMultiselect(
ListUIState expenseListState, RemoveFromExpenseMultiselect action) {
return expenseListState
.rebuild((b) => b..selectedEntities.remove(action.entity));
}
ListUIState _clearListMultiselect(
ListUIState expenseListState, ClearExpenseMultiselect action) {
return expenseListState.rebuild((b) => b..selectedEntities = null);
}
final expensesReducer = combineReducers<ExpenseState>([
TypedReducer<ExpenseState, SaveExpenseSuccess>(_updateExpense),
TypedReducer<ExpenseState, AddExpenseSuccess>(_addExpense),

View File

@ -53,6 +53,12 @@ class _CompanyGatewayListItemState extends State<CompanyGatewayListItem>
final isInMultiselect = listUIState.isInMultiselect();
final showCheckbox = widget.onCheckboxChanged != null || isInMultiselect;
if (isInMultiselect) {
_multiselectCheckboxAnimController.forward();
} else {
_multiselectCheckboxAnimController.animateBack(0.0);
}
return DismissibleEntity(
userCompany: state.userCompany,
entity: widget.companyGateway,

View File

@ -52,6 +52,12 @@ class _DocumentListItemState extends State<DocumentListItem>
final isInMultiselect = listUIState.isInMultiselect();
final showCheckbox = widget.onCheckboxChanged != null || isInMultiselect;
if (isInMultiselect) {
_multiselectCheckboxAnimController.forward();
} else {
_multiselectCheckboxAnimController.animateBack(0.0);
}
return DismissibleEntity(
isSelected: widget.document.id ==
(uiState.isEditing

View File

@ -1,6 +1,8 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/document/document_selectors.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/help_text.dart';
@ -44,6 +46,9 @@ class ExpenseList extends StatelessWidget {
onClearPressed: viewModel.onClearEntityFilterPressed,
));
}
final store = StoreProvider.of<AppState>(context);
final listUIState = store.state.uiState.expenseUIState.listUIState;
final isInMultiselect = listUIState.isInMultiselect();
widgets.add(Expanded(
child: !viewModel.isLoaded
@ -87,7 +92,19 @@ class ExpenseList extends StatelessWidget {
context, [expense], action);
}
},
onLongPress: () => showDialog(),
onLongPress: () async {
final longPressIsSelection = store.state.uiState
.longPressSelectionIsDefault ??
true;
if (longPressIsSelection && !isInMultiselect) {
viewModel.onEntityAction(context, [expense],
EntityAction.toggleMultiselect);
} else {
showDialog();
}
},
isChecked: isInMultiselect &&
listUIState.isSelected(expense),
);
},
),

View File

@ -9,7 +9,7 @@ import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/app/dismissible_entity.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class ExpenseListItem extends StatelessWidget {
class ExpenseListItem extends StatefulWidget {
const ExpenseListItem({
@required this.userCompany,
@required this.onTap,
@ -38,6 +38,12 @@ class ExpenseListItem extends StatelessWidget {
static final expenseItemKey = (int id) => Key('__expense_item_${id}__');
@override
_ExpenseListItemState createState() => _ExpenseListItemState();
}
class _ExpenseListItemState extends State<ExpenseListItem>
with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
@ -45,56 +51,77 @@ class ExpenseListItem extends StatelessWidget {
final uiState = state.uiState;
final expenseUIState = uiState.expenseUIState;
final filterMatch = filter != null && filter.isNotEmpty
? expense.matchesFilterValue(filter)
final filterMatch = widget.filter != null && widget.filter.isNotEmpty
? widget.expense.matchesFilterValue(widget.filter)
: null;
final company = state.selectedCompany;
final category = company.expenseCategoryMap[expense.categoryId];
final category = company.expenseCategoryMap[widget.expense.categoryId];
String subtitle = '';
if (filterMatch != null) {
subtitle = filterMatch;
} else if (client != null || vendor != null || category != null) {
} else if (widget.client != null ||
widget.vendor != null ||
category != null) {
if (category != null) {
subtitle += category.name;
if (vendor != null || client != null) {
if (widget.vendor != null || widget.client != null) {
subtitle += '';
}
}
if (vendor != null) {
subtitle += vendor.name;
if (client != null) {
if (widget.vendor != null) {
subtitle += widget.vendor.name;
if (widget.client != null) {
subtitle += '';
}
}
if (client != null) {
subtitle += client.displayName;
if (widget.client != null) {
subtitle += widget.client.displayName;
}
}
if (hasDocuments) {
if (widget.hasDocuments) {
if (subtitle.isNotEmpty) {
subtitle += ' ';
}
subtitle += '📎';
}
final listUIState = expenseUIState.listUIState;
final isInMultiselect = listUIState.isInMultiselect();
final showCheckbox = widget.onCheckboxChanged != null || isInMultiselect;
if (isInMultiselect) {
_multiselectCheckboxAnimController.forward();
} else {
_multiselectCheckboxAnimController.animateBack(0.0);
}
return DismissibleEntity(
isSelected: expense.id ==
isSelected: widget.expense.id ==
(uiState.isEditing
? expenseUIState.editing.id
: expenseUIState.selectedId),
userCompany: userCompany,
entity: expense,
onEntityAction: onEntityAction,
userCompany: widget.userCompany,
entity: widget.expense,
onEntityAction: widget.onEntityAction,
child: ListTile(
onTap: onTap,
onLongPress: onLongPress,
leading: onCheckboxChanged != null
? Checkbox(
value: isChecked,
onChanged: (value) => onCheckboxChanged(value),
activeColor: Theme.of(context).accentColor,
onTap: isInMultiselect
? () => widget.onEntityAction(EntityAction.toggleMultiselect)
: widget.onTap,
onLongPress: widget.onLongPress,
leading: showCheckbox
? FadeTransition(
opacity: _multiselectCheckboxAnim,
child: IgnorePointer(
ignoring: listUIState.isInMultiselect(),
child: Checkbox(
//key: NinjaKeys.expenseItemCheckbox(task.id),
value: widget.isChecked,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (value) => widget.onCheckboxChanged(value),
activeColor: Theme.of(context).accentColor,
),
),
)
: null,
title: Container(
@ -103,16 +130,16 @@ class ExpenseListItem extends StatelessWidget {
children: <Widget>[
Expanded(
child: Text(
expense.publicNotes.isNotEmpty
? expense.publicNotes
: formatDate(expense.expenseDate, context),
widget.expense.publicNotes.isNotEmpty
? widget.expense.publicNotes
: formatDate(widget.expense.expenseDate, context),
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.title,
),
),
Text(
formatNumber(expense.amountWithTax, context,
currencyId: expense.expenseCurrencyId),
formatNumber(widget.expense.amountWithTax, context,
currencyId: widget.expense.expenseCurrencyId),
style: Theme.of(context).textTheme.title)
],
),
@ -131,16 +158,37 @@ class ExpenseListItem extends StatelessWidget {
)
: Container(),
),
Text(localization.lookup('expense_status_${expense.statusId}'),
Text(
localization
.lookup('expense_status_${widget.expense.statusId}'),
style: TextStyle(
color: ExpenseStatusColors.colors[expense.statusId],
color:
ExpenseStatusColors.colors[widget.expense.statusId],
)),
],
),
EntityStateLabel(expense),
EntityStateLabel(widget.expense),
],
),
),
);
}
Animation _multiselectCheckboxAnim;
AnimationController _multiselectCheckboxAnimController;
@override
void initState() {
super.initState();
_multiselectCheckboxAnimController =
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
_multiselectCheckboxAnim = Tween<double>(begin: 0.0, end: 1.0)
.animate(_multiselectCheckboxAnimController);
}
@override
void dispose() {
_multiselectCheckboxAnimController.dispose();
super.dispose();
}
}

View File

@ -98,7 +98,7 @@ class ExpenseListVM {
},
onEntityAction: (BuildContext context, List<BaseEntity> expenses,
EntityAction action) =>
handleExpenseAction(context, expenses[0], action),
handleExpenseAction(context, expenses, action),
onRefreshed: (context) => _handleRefresh(context),
);
}

View File

@ -1,19 +1,29 @@
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/ui/app/app_scaffold.dart';
import 'package:invoiceninja_flutter/ui/app/list_filter.dart';
import 'package:invoiceninja_flutter/ui/app/list_filter_button.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/ui/expense/expense_list_vm.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_actions.dart';
import 'package:invoiceninja_flutter/ui/app/app_bottom_bar.dart';
import 'package:invoiceninja_flutter/ui/app/app_scaffold.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart';
import 'package:invoiceninja_flutter/ui/app/list_filter.dart';
import 'package:invoiceninja_flutter/ui/app/list_filter_button.dart';
import 'package:invoiceninja_flutter/ui/expense/expense_list_vm.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
import 'expense_screen_vm.dart';
class ExpenseScreen extends StatelessWidget {
const ExpenseScreen({
Key key,
@required this.viewModel,
}) : super(key: key);
static const String route = '/expense';
final ExpenseScreenVM viewModel;
@override
Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context);
@ -21,8 +31,22 @@ class ExpenseScreen extends StatelessWidget {
final company = state.selectedCompany;
final userCompany = state.userCompany;
final localization = AppLocalization.of(context);
final listUIState = state.uiState.expenseUIState.listUIState;
final isInMultiselect = listUIState.isInMultiselect();
return AppScaffold(
isChecked: isInMultiselect &&
listUIState.selectedEntities.length == viewModel.expenseList.length,
showCheckbox: isInMultiselect,
onCheckboxChanged: (value) {
final expenses = viewModel.expenseList
.map<ExpenseEntity>((expenseId) => viewModel.expenseMap[expenseId])
.where((expense) => value != listUIState.isSelected(expense))
.toList();
viewModel.onEntityAction(
context, expenses, EntityAction.toggleMultiselect);
},
appBarTitle: ListFilter(
key: ValueKey(store.state.expenseListState.filterClearedAt),
entityType: EntityType.expense,
@ -31,12 +55,44 @@ class ExpenseScreen extends StatelessWidget {
},
),
appBarActions: [
ListFilterButton(
entityType: EntityType.expense,
onFilterPressed: (String value) {
store.dispatch(FilterExpenses(value));
},
),
if (!viewModel.isInMultiselect)
ListFilterButton(
entityType: EntityType.expense,
onFilterPressed: (String value) {
store.dispatch(FilterExpenses(value));
},
),
if (viewModel.isInMultiselect)
FlatButton(
key: key,
child: Text(
localization.cancel,
style: TextStyle(color: Colors.white),
),
onPressed: () {
store.dispatch(ClearExpenseMultiselect(context: context));
},
),
if (viewModel.isInMultiselect)
FlatButton(
key: key,
textColor: Colors.white,
disabledTextColor: Colors.white54,
child: Text(
localization.done,
),
onPressed: state.expenseListState.selectedEntities.isEmpty
? null
: () async {
await showEntityActionsDialog(
entities: state.expenseListState.selectedEntities,
userCompany: userCompany,
context: context,
onEntityAction: viewModel.onEntityAction,
multiselect: true);
store.dispatch(ClearExpenseMultiselect(context: context));
},
),
],
body: ExpenseListBuilder(),
bottomNavigationBar: AppBottomBar(

View File

@ -0,0 +1,64 @@
import 'package:built_collection/built_collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja_flutter/data/models/models.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_actions.dart';
import 'package:invoiceninja_flutter/redux/expense/expense_selectors.dart';
import 'package:redux/redux.dart';
import 'expense_screen.dart';
class ExpenseScreenBuilder extends StatelessWidget {
const ExpenseScreenBuilder({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, ExpenseScreenVM>(
//rebuildOnChange: true,
converter: ExpenseScreenVM.fromStore,
builder: (context, vm) {
return ExpenseScreen(
viewModel: vm,
);
},
);
}
}
class ExpenseScreenVM {
ExpenseScreenVM({
@required this.isInMultiselect,
@required this.expenseList,
@required this.userCompany,
@required this.onEntityAction,
@required this.expenseMap,
});
final bool isInMultiselect;
final UserCompanyEntity userCompany;
final List<String> expenseList;
final Function(BuildContext, List<BaseEntity>, EntityAction) onEntityAction;
final BuiltMap<String, ExpenseEntity> expenseMap;
static ExpenseScreenVM fromStore(Store<AppState> store) {
final state = store.state;
return ExpenseScreenVM(
expenseMap: state.expenseState.map,
expenseList: memoizedFilteredExpenseList(
state.expenseState.map,
state.clientState.map,
state.vendorState.map,
state.expenseState.list,
state.expenseListState),
userCompany: state.userCompany,
isInMultiselect: state.expenseListState.isInMultiselect(),
onEntityAction: (BuildContext context, List<BaseEntity> expenses,
EntityAction action) =>
handleExpenseAction(context, expenses, action),
);
}
}

View File

@ -146,7 +146,7 @@ class ExpenseViewVM {
}
},
onEntityAction: (BuildContext context, EntityAction action) =>
handleExpenseAction(context, expense, action),
handleExpenseAction(context, [expense], action),
onUploadDocument: (BuildContext context, String path) {
final Completer<DocumentEntity> completer =
Completer<DocumentEntity>();