diff --git a/lib/ui/dashboard/dashboard_sidebar.dart b/lib/ui/dashboard/dashboard_sidebar.dart index b1297899d..ec028f61d 100644 --- a/lib/ui/dashboard/dashboard_sidebar.dart +++ b/lib/ui/dashboard/dashboard_sidebar.dart @@ -8,6 +8,7 @@ import 'package:invoiceninja_flutter/redux/dashboard/dashboard_actions.dart'; import 'package:invoiceninja_flutter/redux/dashboard/dashboard_sidebar_selectors.dart'; import 'package:invoiceninja_flutter/ui/app/help_text.dart'; import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart'; +import 'package:invoiceninja_flutter/ui/expense/expense_list_item.dart'; import 'package:invoiceninja_flutter/ui/invoice/invoice_list_item.dart'; import 'package:invoiceninja_flutter/ui/payment/payment_list_item.dart'; import 'package:invoiceninja_flutter/ui/quote/quote_list_item.dart'; @@ -369,29 +370,31 @@ class ExpenseSidbar extends StatelessWidget { final localization = AppLocalization.of(context); final store = StoreProvider.of(context); final state = store.state; - final runningTasks = memoizedRunningTasks( - state.taskState.map, + + /* + final recentExpenses = memoizedRecentExpenses( + state.expenseState.map, state.clientState.map, ); - final recentTasks = memoizedRecentTasks( - state.taskState.map, + final upcomingExpenses = memoizedUpcomingExpenses( + state.expenseState.map, state.clientState.map, ); final selectedIds = - state.dashboardUIState.selectedEntities[EntityType.task]; + state.dashboardUIState.selectedEntities[EntityType.expense]; return _DashboardSidebar( entityType: EntityType.quote, - label1: localization.runningTasks + - (runningTasks.isNotEmpty ? ' (${runningTasks.length})' : ''), - list1: runningTasks.isEmpty + label1: localization.upcomingExpenses + + (upcomingExpenses.isNotEmpty ? ' (${upcomingExpenses.length})' : ''), + list1: upcomingExpenses.isEmpty ? null : ListView.separated( shrinkWrap: true, - itemCount: runningTasks.length, + itemCount: upcomingExpenses.length, itemBuilder: (BuildContext context, int index) { - return TaskListItem( - task: runningTasks[index], + return ExpenseListItem( + expense: upcomingExpenses[index], showCheckbox: false, ); }, @@ -432,6 +435,7 @@ class ExpenseSidbar extends StatelessWidget { separatorBuilder: (context, index) => ListDivider(), ), ); + */ } } diff --git a/lib/ui/expense/expense_list_item.dart b/lib/ui/expense/expense_list_item.dart index 19c28a6e1..8c02659b3 100644 --- a/lib/ui/expense/expense_list_item.dart +++ b/lib/ui/expense/expense_list_item.dart @@ -14,24 +14,22 @@ import 'package:invoiceninja_flutter/utils/platforms.dart'; class ExpenseListItem extends StatelessWidget { const ExpenseListItem({ - @required this.user, @required this.expense, - @required this.filter, + this.filter, this.onTap, - this.onLongPress, this.onCheckboxChanged, - this.isChecked = false, + this.showCheckbox = false, this.isDismissible = true, + this.isChecked = false, }); - final UserEntity user; + final Function(bool) onCheckboxChanged; final GestureTapCallback onTap; - final GestureTapCallback onLongPress; final ExpenseEntity expense; final String filter; - final Function(bool) onCheckboxChanged; - final bool isChecked; + final bool showCheckbox; final bool isDismissible; + final bool isChecked; @override Widget build(BuildContext context) { @@ -49,6 +47,9 @@ class ExpenseListItem extends StatelessWidget { final listUIState = expenseUIState.listUIState; final isInMultiselect = listUIState.isInMultiselect(); final showCheckbox = onCheckboxChanged != null || isInMultiselect; + final isChecked = isDismissible + ? (isInMultiselect && listUIState.isSelected(expense.id)) + : this.isChecked; final textStyle = TextStyle(fontSize: 16); final textColor = Theme.of(context).textTheme.bodyText1.color; @@ -90,11 +91,12 @@ class ExpenseListItem extends StatelessWidget { ? InkWell( onTap: () => onTap != null ? onTap() - : selectEntity(entity: expense, context: context), - onLongPress: () => onLongPress != null - ? onLongPress() : selectEntity( - entity: expense, context: context, longPress: true), + entity: expense, + context: context, + forceView: !showCheckbox), + onLongPress: () => selectEntity( + entity: expense, context: context, longPress: true), child: Padding( padding: const EdgeInsets.only( left: 10, @@ -185,11 +187,12 @@ class ExpenseListItem extends StatelessWidget { : ListTile( onTap: () => onTap != null ? onTap() - : selectEntity(entity: expense, context: context), - onLongPress: () => onLongPress != null - ? onLongPress() : selectEntity( - entity: expense, context: context, longPress: true), + entity: expense, + context: context, + forceView: !showCheckbox), + onLongPress: () => selectEntity( + entity: expense, context: context, longPress: true), leading: showCheckbox ? IgnorePointer( ignoring: listUIState.isInMultiselect(), diff --git a/lib/ui/expense/expense_list_vm.dart b/lib/ui/expense/expense_list_vm.dart index 1334d4b2e..9157ed31c 100644 --- a/lib/ui/expense/expense_list_vm.dart +++ b/lib/ui/expense/expense_list_vm.dart @@ -37,16 +37,10 @@ class ExpenseListBuilder extends StatelessWidget { itemBuilder: (BuildContext context, index) { final expenseId = viewModel.expenseList[index]; final expense = viewModel.expenseMap[expenseId]; - final state = viewModel.state; - final listUIState = state.getListState(EntityType.expense); - final isInMultiselect = listUIState.isInMultiselect(); return ExpenseListItem( - user: state.user, filter: viewModel.filter, expense: expense, - isChecked: - isInMultiselect && listUIState.isSelected(expense.id), ); }); }, diff --git a/lib/ui/invoice/edit/invoice_item_selector.dart b/lib/ui/invoice/edit/invoice_item_selector.dart index b5c3467aa..647a6b959 100644 --- a/lib/ui/invoice/edit/invoice_item_selector.dart +++ b/lib/ui/invoice/edit/invoice_item_selector.dart @@ -205,10 +205,9 @@ class _InvoiceItemSelectorState extends State return ProductListItem( isDismissible: false, onCheckboxChanged: (checked) => _toggleEntity(product), - isChecked: _selected.contains(product), product: product, - userCompany: state.userCompany, filter: _filter, + isChecked: _selected.contains(product), onTap: () { if (_selected.isNotEmpty) { _toggleEntity(product); @@ -247,6 +246,15 @@ class _InvoiceItemSelectorState extends State isDismissible: false, task: task, filter: _filter, + isChecked: _selected.contains(task), + onTap: () { + if (_selected.isNotEmpty) { + _toggleEntity(task); + } else { + _selected.add(task); + _onItemsSelected(context); + } + }, ); }, ); @@ -271,7 +279,6 @@ class _InvoiceItemSelectorState extends State final expense = state.expenseState.map[entityId] ?? ExpenseEntity(); return ExpenseListItem( isDismissible: false, - user: state.user, onCheckboxChanged: (checked) => _toggleEntity(expense), isChecked: _selected.contains(expense), expense: expense, diff --git a/lib/ui/product/product_list_item.dart b/lib/ui/product/product_list_item.dart index 0c533dc94..0a8c11ff7 100644 --- a/lib/ui/product/product_list_item.dart +++ b/lib/ui/product/product_list_item.dart @@ -13,7 +13,6 @@ import 'package:invoiceninja_flutter/utils/platforms.dart'; class ProductListItem extends StatelessWidget { const ProductListItem({ - @required this.userCompany, @required this.product, @required this.filter, this.onTap, @@ -23,14 +22,11 @@ class ProductListItem extends StatelessWidget { this.isDismissible = true, }); - final UserCompanyEntity userCompany; final GestureTapCallback onTap; final GestureTapCallback onLongPress; final Function(bool) onCheckboxChanged; final bool isChecked; final bool isDismissible; - - //final ValueChanged onCheckboxChanged; final ProductEntity product; final String filter; @@ -56,7 +52,7 @@ class ProductListItem extends StatelessWidget { (uiState.isEditing ? productUIState.editing.id : productUIState.selectedId), - userCompany: userCompany, + userCompany: state.userCompany, entity: product, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { diff --git a/lib/ui/product/product_list_vm.dart b/lib/ui/product/product_list_vm.dart index 5970605d5..e92626ed1 100644 --- a/lib/ui/product/product_list_vm.dart +++ b/lib/ui/product/product_list_vm.dart @@ -42,7 +42,6 @@ class ProductListBuilder extends StatelessWidget { final isInMultiselect = listState.isInMultiselect(); return ProductListItem( - userCompany: viewModel.state.userCompany, filter: viewModel.filter, product: product, isChecked: isInMultiselect && listState.isSelected(product.id), diff --git a/lib/ui/task/task_list_item.dart b/lib/ui/task/task_list_item.dart index d98427934..fc53b2090 100644 --- a/lib/ui/task/task_list_item.dart +++ b/lib/ui/task/task_list_item.dart @@ -19,14 +19,20 @@ class TaskListItem extends StatelessWidget { const TaskListItem({ @required this.task, this.filter, + this.onTap, + this.onCheckboxChanged, this.isDismissible = true, - this.showCheckbox = true, + this.showCheckbox = false, + this.isChecked = false, }); + final Function(bool) onCheckboxChanged; + final GestureTapCallback onTap; final TaskEntity task; final String filter; final bool showCheckbox; final bool isDismissible; + final bool isChecked; @override Widget build(BuildContext context) { @@ -39,8 +45,11 @@ class TaskListItem extends StatelessWidget { ? (task.matchesFilterValue(filter) ?? client.matchesFilterValue(filter)) : null; final listUIState = taskUIState.listUIState; - final isInMultiselect = showCheckbox && listUIState.isInMultiselect(); - final isChecked = isInMultiselect && listUIState.isSelected(task.id); + final isInMultiselect = listUIState.isInMultiselect(); + final showCheckbox = onCheckboxChanged != null || isInMultiselect; + final isChecked = isDismissible + ? (isInMultiselect && listUIState.isSelected(task.id)) + : this.isChecked; final textStyle = TextStyle(fontSize: 16); final subtitle = client.displayName; final textColor = Theme.of(context).textTheme.bodyText1.color; @@ -83,11 +92,13 @@ class TaskListItem extends StatelessWidget { builder: (BuildContext context, BoxConstraints constraints) { return constraints.maxWidth > kTableListWidthCutoff ? InkWell( - onTap: () => selectEntity( - entity: task, - context: context, - forceView: !showCheckbox, - ), + onTap: () => onTap != null + ? onTap() + : selectEntity( + entity: task, + context: context, + forceView: !showCheckbox, + ), onLongPress: () => selectEntity( entity: task, context: context, @@ -113,7 +124,8 @@ class TaskListItem extends StatelessWidget { value: isChecked, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onChanged: (value) => null, + onChanged: (value) => + onCheckboxChanged(value), activeColor: Theme.of(context).accentColor, ), ), @@ -178,11 +190,13 @@ class TaskListItem extends StatelessWidget { ), ) : ListTile( - onTap: () => selectEntity( - entity: task, - context: context, - forceView: !showCheckbox, - ), + onTap: () => onTap != null + ? onTap() + : selectEntity( + entity: task, + context: context, + forceView: !showCheckbox, + ), onLongPress: () => selectEntity( entity: task, context: context, longPress: true), leading: showCheckbox @@ -192,7 +206,7 @@ class TaskListItem extends StatelessWidget { value: isChecked, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - onChanged: (value) => null, + onChanged: (value) => onCheckboxChanged(value), activeColor: Theme.of(context).accentColor, ), ) diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index dd937680a..6f43691fd 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -18,6 +18,7 @@ mixin LocalizationsProvider on LocaleCodeAware { 'running_tasks': 'Running Tasks', 'recent_tasks': 'Recent Tasks', 'recent_expenses': 'Recent Expenses', + 'upcoming_expenses': 'Upcoming Expenses', 'update_app': 'Update App', 'started_import': 'Successfully started import', 'duplicate_column_mapping': 'Duplicate column mapping', @@ -5332,6 +5333,9 @@ mixin LocalizationsProvider on LocaleCodeAware { String get recentExpenses => _localizedValues[localeCode]['recent_expenses'] ?? ''; + String get upcomingExpenses => + _localizedValues[localeCode]['upcoming_expenses'] ?? ''; + String lookup(String key) { final lookupKey = toSnakeCase(key);