Add task and expense tabs to dashboard

This commit is contained in:
Hillel Coren 2020-12-30 15:41:57 +02:00
parent 790c032693
commit 8da124d1ca
8 changed files with 78 additions and 57 deletions

View File

@ -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/redux/dashboard/dashboard_sidebar_selectors.dart';
import 'package:invoiceninja_flutter/ui/app/help_text.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/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/invoice/invoice_list_item.dart';
import 'package:invoiceninja_flutter/ui/payment/payment_list_item.dart'; import 'package:invoiceninja_flutter/ui/payment/payment_list_item.dart';
import 'package:invoiceninja_flutter/ui/quote/quote_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 localization = AppLocalization.of(context);
final store = StoreProvider.of<AppState>(context); final store = StoreProvider.of<AppState>(context);
final state = store.state; final state = store.state;
final runningTasks = memoizedRunningTasks(
state.taskState.map, /*
final recentExpenses = memoizedRecentExpenses(
state.expenseState.map,
state.clientState.map, state.clientState.map,
); );
final recentTasks = memoizedRecentTasks( final upcomingExpenses = memoizedUpcomingExpenses(
state.taskState.map, state.expenseState.map,
state.clientState.map, state.clientState.map,
); );
final selectedIds = final selectedIds =
state.dashboardUIState.selectedEntities[EntityType.task]; state.dashboardUIState.selectedEntities[EntityType.expense];
return _DashboardSidebar( return _DashboardSidebar(
entityType: EntityType.quote, entityType: EntityType.quote,
label1: localization.runningTasks + label1: localization.upcomingExpenses +
(runningTasks.isNotEmpty ? ' (${runningTasks.length})' : ''), (upcomingExpenses.isNotEmpty ? ' (${upcomingExpenses.length})' : ''),
list1: runningTasks.isEmpty list1: upcomingExpenses.isEmpty
? null ? null
: ListView.separated( : ListView.separated(
shrinkWrap: true, shrinkWrap: true,
itemCount: runningTasks.length, itemCount: upcomingExpenses.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return TaskListItem( return ExpenseListItem(
task: runningTasks[index], expense: upcomingExpenses[index],
showCheckbox: false, showCheckbox: false,
); );
}, },
@ -432,6 +435,7 @@ class ExpenseSidbar extends StatelessWidget {
separatorBuilder: (context, index) => ListDivider(), separatorBuilder: (context, index) => ListDivider(),
), ),
); );
*/
} }
} }

View File

@ -14,24 +14,22 @@ import 'package:invoiceninja_flutter/utils/platforms.dart';
class ExpenseListItem extends StatelessWidget { class ExpenseListItem extends StatelessWidget {
const ExpenseListItem({ const ExpenseListItem({
@required this.user,
@required this.expense, @required this.expense,
@required this.filter, this.filter,
this.onTap, this.onTap,
this.onLongPress,
this.onCheckboxChanged, this.onCheckboxChanged,
this.isChecked = false, this.showCheckbox = false,
this.isDismissible = true, this.isDismissible = true,
this.isChecked = false,
}); });
final UserEntity user; final Function(bool) onCheckboxChanged;
final GestureTapCallback onTap; final GestureTapCallback onTap;
final GestureTapCallback onLongPress;
final ExpenseEntity expense; final ExpenseEntity expense;
final String filter; final String filter;
final Function(bool) onCheckboxChanged; final bool showCheckbox;
final bool isChecked;
final bool isDismissible; final bool isDismissible;
final bool isChecked;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -49,6 +47,9 @@ class ExpenseListItem extends StatelessWidget {
final listUIState = expenseUIState.listUIState; final listUIState = expenseUIState.listUIState;
final isInMultiselect = listUIState.isInMultiselect(); final isInMultiselect = listUIState.isInMultiselect();
final showCheckbox = onCheckboxChanged != null || isInMultiselect; final showCheckbox = onCheckboxChanged != null || isInMultiselect;
final isChecked = isDismissible
? (isInMultiselect && listUIState.isSelected(expense.id))
: this.isChecked;
final textStyle = TextStyle(fontSize: 16); final textStyle = TextStyle(fontSize: 16);
final textColor = Theme.of(context).textTheme.bodyText1.color; final textColor = Theme.of(context).textTheme.bodyText1.color;
@ -90,10 +91,11 @@ class ExpenseListItem extends StatelessWidget {
? InkWell( ? InkWell(
onTap: () => onTap != null onTap: () => onTap != null
? onTap() ? onTap()
: selectEntity(entity: expense, context: context),
onLongPress: () => onLongPress != null
? onLongPress()
: selectEntity( : selectEntity(
entity: expense,
context: context,
forceView: !showCheckbox),
onLongPress: () => selectEntity(
entity: expense, context: context, longPress: true), entity: expense, context: context, longPress: true),
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
@ -185,10 +187,11 @@ class ExpenseListItem extends StatelessWidget {
: ListTile( : ListTile(
onTap: () => onTap != null onTap: () => onTap != null
? onTap() ? onTap()
: selectEntity(entity: expense, context: context),
onLongPress: () => onLongPress != null
? onLongPress()
: selectEntity( : selectEntity(
entity: expense,
context: context,
forceView: !showCheckbox),
onLongPress: () => selectEntity(
entity: expense, context: context, longPress: true), entity: expense, context: context, longPress: true),
leading: showCheckbox leading: showCheckbox
? IgnorePointer( ? IgnorePointer(

View File

@ -37,16 +37,10 @@ class ExpenseListBuilder extends StatelessWidget {
itemBuilder: (BuildContext context, index) { itemBuilder: (BuildContext context, index) {
final expenseId = viewModel.expenseList[index]; final expenseId = viewModel.expenseList[index];
final expense = viewModel.expenseMap[expenseId]; final expense = viewModel.expenseMap[expenseId];
final state = viewModel.state;
final listUIState = state.getListState(EntityType.expense);
final isInMultiselect = listUIState.isInMultiselect();
return ExpenseListItem( return ExpenseListItem(
user: state.user,
filter: viewModel.filter, filter: viewModel.filter,
expense: expense, expense: expense,
isChecked:
isInMultiselect && listUIState.isSelected(expense.id),
); );
}); });
}, },

View File

@ -205,10 +205,9 @@ class _InvoiceItemSelectorState extends State<InvoiceItemSelector>
return ProductListItem( return ProductListItem(
isDismissible: false, isDismissible: false,
onCheckboxChanged: (checked) => _toggleEntity(product), onCheckboxChanged: (checked) => _toggleEntity(product),
isChecked: _selected.contains(product),
product: product, product: product,
userCompany: state.userCompany,
filter: _filter, filter: _filter,
isChecked: _selected.contains(product),
onTap: () { onTap: () {
if (_selected.isNotEmpty) { if (_selected.isNotEmpty) {
_toggleEntity(product); _toggleEntity(product);
@ -247,6 +246,15 @@ class _InvoiceItemSelectorState extends State<InvoiceItemSelector>
isDismissible: false, isDismissible: false,
task: task, task: task,
filter: _filter, 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<InvoiceItemSelector>
final expense = state.expenseState.map[entityId] ?? ExpenseEntity(); final expense = state.expenseState.map[entityId] ?? ExpenseEntity();
return ExpenseListItem( return ExpenseListItem(
isDismissible: false, isDismissible: false,
user: state.user,
onCheckboxChanged: (checked) => _toggleEntity(expense), onCheckboxChanged: (checked) => _toggleEntity(expense),
isChecked: _selected.contains(expense), isChecked: _selected.contains(expense),
expense: expense, expense: expense,

View File

@ -13,7 +13,6 @@ import 'package:invoiceninja_flutter/utils/platforms.dart';
class ProductListItem extends StatelessWidget { class ProductListItem extends StatelessWidget {
const ProductListItem({ const ProductListItem({
@required this.userCompany,
@required this.product, @required this.product,
@required this.filter, @required this.filter,
this.onTap, this.onTap,
@ -23,14 +22,11 @@ class ProductListItem extends StatelessWidget {
this.isDismissible = true, this.isDismissible = true,
}); });
final UserCompanyEntity userCompany;
final GestureTapCallback onTap; final GestureTapCallback onTap;
final GestureTapCallback onLongPress; final GestureTapCallback onLongPress;
final Function(bool) onCheckboxChanged; final Function(bool) onCheckboxChanged;
final bool isChecked; final bool isChecked;
final bool isDismissible; final bool isDismissible;
//final ValueChanged<bool> onCheckboxChanged;
final ProductEntity product; final ProductEntity product;
final String filter; final String filter;
@ -56,7 +52,7 @@ class ProductListItem extends StatelessWidget {
(uiState.isEditing (uiState.isEditing
? productUIState.editing.id ? productUIState.editing.id
: productUIState.selectedId), : productUIState.selectedId),
userCompany: userCompany, userCompany: state.userCompany,
entity: product, entity: product,
child: LayoutBuilder( child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) { builder: (BuildContext context, BoxConstraints constraints) {

View File

@ -42,7 +42,6 @@ class ProductListBuilder extends StatelessWidget {
final isInMultiselect = listState.isInMultiselect(); final isInMultiselect = listState.isInMultiselect();
return ProductListItem( return ProductListItem(
userCompany: viewModel.state.userCompany,
filter: viewModel.filter, filter: viewModel.filter,
product: product, product: product,
isChecked: isInMultiselect && listState.isSelected(product.id), isChecked: isInMultiselect && listState.isSelected(product.id),

View File

@ -19,14 +19,20 @@ class TaskListItem extends StatelessWidget {
const TaskListItem({ const TaskListItem({
@required this.task, @required this.task,
this.filter, this.filter,
this.onTap,
this.onCheckboxChanged,
this.isDismissible = true, this.isDismissible = true,
this.showCheckbox = true, this.showCheckbox = false,
this.isChecked = false,
}); });
final Function(bool) onCheckboxChanged;
final GestureTapCallback onTap;
final TaskEntity task; final TaskEntity task;
final String filter; final String filter;
final bool showCheckbox; final bool showCheckbox;
final bool isDismissible; final bool isDismissible;
final bool isChecked;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -39,8 +45,11 @@ class TaskListItem extends StatelessWidget {
? (task.matchesFilterValue(filter) ?? client.matchesFilterValue(filter)) ? (task.matchesFilterValue(filter) ?? client.matchesFilterValue(filter))
: null; : null;
final listUIState = taskUIState.listUIState; final listUIState = taskUIState.listUIState;
final isInMultiselect = showCheckbox && listUIState.isInMultiselect(); final isInMultiselect = listUIState.isInMultiselect();
final isChecked = isInMultiselect && listUIState.isSelected(task.id); final showCheckbox = onCheckboxChanged != null || isInMultiselect;
final isChecked = isDismissible
? (isInMultiselect && listUIState.isSelected(task.id))
: this.isChecked;
final textStyle = TextStyle(fontSize: 16); final textStyle = TextStyle(fontSize: 16);
final subtitle = client.displayName; final subtitle = client.displayName;
final textColor = Theme.of(context).textTheme.bodyText1.color; final textColor = Theme.of(context).textTheme.bodyText1.color;
@ -83,7 +92,9 @@ class TaskListItem extends StatelessWidget {
builder: (BuildContext context, BoxConstraints constraints) { builder: (BuildContext context, BoxConstraints constraints) {
return constraints.maxWidth > kTableListWidthCutoff return constraints.maxWidth > kTableListWidthCutoff
? InkWell( ? InkWell(
onTap: () => selectEntity( onTap: () => onTap != null
? onTap()
: selectEntity(
entity: task, entity: task,
context: context, context: context,
forceView: !showCheckbox, forceView: !showCheckbox,
@ -113,7 +124,8 @@ class TaskListItem extends StatelessWidget {
value: isChecked, value: isChecked,
materialTapTargetSize: materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap, MaterialTapTargetSize.shrinkWrap,
onChanged: (value) => null, onChanged: (value) =>
onCheckboxChanged(value),
activeColor: Theme.of(context).accentColor, activeColor: Theme.of(context).accentColor,
), ),
), ),
@ -178,7 +190,9 @@ class TaskListItem extends StatelessWidget {
), ),
) )
: ListTile( : ListTile(
onTap: () => selectEntity( onTap: () => onTap != null
? onTap()
: selectEntity(
entity: task, entity: task,
context: context, context: context,
forceView: !showCheckbox, forceView: !showCheckbox,
@ -192,7 +206,7 @@ class TaskListItem extends StatelessWidget {
value: isChecked, value: isChecked,
materialTapTargetSize: materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap, MaterialTapTargetSize.shrinkWrap,
onChanged: (value) => null, onChanged: (value) => onCheckboxChanged(value),
activeColor: Theme.of(context).accentColor, activeColor: Theme.of(context).accentColor,
), ),
) )

View File

@ -18,6 +18,7 @@ mixin LocalizationsProvider on LocaleCodeAware {
'running_tasks': 'Running Tasks', 'running_tasks': 'Running Tasks',
'recent_tasks': 'Recent Tasks', 'recent_tasks': 'Recent Tasks',
'recent_expenses': 'Recent Expenses', 'recent_expenses': 'Recent Expenses',
'upcoming_expenses': 'Upcoming Expenses',
'update_app': 'Update App', 'update_app': 'Update App',
'started_import': 'Successfully started import', 'started_import': 'Successfully started import',
'duplicate_column_mapping': 'Duplicate column mapping', 'duplicate_column_mapping': 'Duplicate column mapping',
@ -5332,6 +5333,9 @@ mixin LocalizationsProvider on LocaleCodeAware {
String get recentExpenses => String get recentExpenses =>
_localizedValues[localeCode]['recent_expenses'] ?? ''; _localizedValues[localeCode]['recent_expenses'] ?? '';
String get upcomingExpenses =>
_localizedValues[localeCode]['upcoming_expenses'] ?? '';
String lookup(String key) { String lookup(String key) {
final lookupKey = toSnakeCase(key); final lookupKey = toSnakeCase(key);