Add task and expense tabs to dashboard
This commit is contained in:
parent
790c032693
commit
8da124d1ca
|
|
@ -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(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue