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/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<AppState>(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(),
),
);
*/
}
}

View File

@ -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(),

View File

@ -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),
);
});
},

View File

@ -205,10 +205,9 @@ class _InvoiceItemSelectorState extends State<InvoiceItemSelector>
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<InvoiceItemSelector>
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<InvoiceItemSelector>
final expense = state.expenseState.map[entityId] ?? ExpenseEntity();
return ExpenseListItem(
isDismissible: false,
user: state.user,
onCheckboxChanged: (checked) => _toggleEntity(expense),
isChecked: _selected.contains(expense),
expense: expense,

View File

@ -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<bool> 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) {

View File

@ -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),

View File

@ -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,
),
)

View File

@ -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);