invoice/lib/ui/dashboard/dashboard_sidebar.dart

542 lines
19 KiB
Dart

// Flutter imports:
import 'package:flutter/material.dart';
// Package imports:
import 'package:flutter_redux/flutter_redux.dart';
// Project imports:
import 'package:invoiceninja_flutter/constants.dart';
import 'package:invoiceninja_flutter/data/models/entities.dart';
import 'package:invoiceninja_flutter/redux/app/app_state.dart';
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/app/scrollable_listview.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';
import 'package:invoiceninja_flutter/ui/task/task_list_item.dart';
import 'package:invoiceninja_flutter/utils/localization.dart';
class SidebarScaffold extends StatelessWidget {
const SidebarScaffold({
@required this.tabController,
});
final TabController tabController;
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final company = state.company;
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: Row(
children: [
Expanded(
child: TabBar(
isScrollable: true,
controller: tabController,
tabs: [
if (company.isModuleEnabled(EntityType.invoice))
Tab(
text: localization.invoices,
),
if (company.isModuleEnabled(EntityType.payment))
Tab(
text: localization.payments,
),
if (company.isModuleEnabled(EntityType.quote))
Tab(
text: localization.quotes,
),
if (company.isModuleEnabled(EntityType.task))
Tab(
text: localization.tasks,
),
if (company.isModuleEnabled(EntityType.expense))
Tab(
text: localization.expenses,
),
],
),
),
IconButton(
icon: Icon(Icons.clear),
onPressed: () => store.dispatch(
UpdateDashboardSidebar(showSidebar: false),
),
),
],
),
),
body: TabBarView(
controller: tabController,
children: [
if (company.isModuleEnabled(EntityType.invoice)) InvoiceSidebar(),
if (company.isModuleEnabled(EntityType.payment)) PaymentSidebar(),
if (company.isModuleEnabled(EntityType.quote)) QuoteSidebar(),
if (company.isModuleEnabled(EntityType.task)) TaskSidebar(),
if (company.isModuleEnabled(EntityType.expense)) ExpenseSidbar(),
],
),
);
}
}
class InvoiceSidebar extends StatelessWidget {
const InvoiceSidebar();
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final upcomingInvoices = memoizedUpcomingInvoices(
state.invoiceState.map,
state.clientState.map,
);
final pastDueInvoices = memoizedPastDueInvoices(
state.invoiceState.map,
state.clientState.map,
);
final selectedIds =
state.dashboardUIState.selectedEntities[EntityType.invoice];
return _DashboardSidebar(
entityType: EntityType.invoice,
label1: localization.upcomingInvoices +
(upcomingInvoices.isNotEmpty ? ' (${upcomingInvoices.length})' : ''),
list1: upcomingInvoices.isEmpty
? null
: ScrollableListViewBuilder(
itemCount: upcomingInvoices.length,
itemBuilder: (BuildContext context, int index) {
return InvoiceListItem(
invoice: upcomingInvoices[index],
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
label2: localization.pastDueInvoices +
(pastDueInvoices.isNotEmpty ? ' (${pastDueInvoices.length})' : ''),
list2: pastDueInvoices.isEmpty
? null
: ScrollableListViewBuilder(
itemCount: pastDueInvoices.length,
itemBuilder: (BuildContext context, int index) {
return InvoiceListItem(
invoice: pastDueInvoices[index],
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
label3: (selectedIds ?? <String>[]).isEmpty
? null
: localization.selectedInvoices + ' (${selectedIds.length})',
list3: (selectedIds ?? <String>[]).isEmpty
? null
: ScrollableListViewBuilder(
itemCount: selectedIds?.length,
itemBuilder: (BuildContext context, int index) {
final invoice = state.invoiceState.map[selectedIds[index]];
return invoice == null
? SizedBox()
: InvoiceListItem(
invoice: invoice,
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
);
}
}
class PaymentSidebar extends StatelessWidget {
const PaymentSidebar();
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final recentPayments = memoizedRecentPayments(
state.paymentState.map,
state.clientState.map,
);
final selectedIds =
state.dashboardUIState.selectedEntities[EntityType.payment];
return _DashboardSidebar(
entityType: EntityType.payment,
label1: localization.recentPayments +
(recentPayments.isNotEmpty ? ' (${recentPayments.length})' : ''),
list1: recentPayments.isEmpty
? null
: ScrollableListViewBuilder(
itemCount: recentPayments.length,
itemBuilder: (BuildContext context, int index) {
return PaymentListItem(
payment: recentPayments[index],
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
label3: (selectedIds ?? <String>[]).isEmpty
? null
: localization.selectedPayments + ' (${selectedIds.length})',
list3: (selectedIds ?? <String>[]).isEmpty
? null
: ScrollableListViewBuilder(
itemCount: selectedIds?.length,
itemBuilder: (BuildContext context, int index) {
final payment = state.paymentState.map[selectedIds[index]];
return payment == null
? SizedBox()
: PaymentListItem(
payment: payment,
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
);
}
}
class QuoteSidebar extends StatelessWidget {
const QuoteSidebar();
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final upcomingQuotes = memoizedUpcomingQuotes(
state.quoteState.map,
state.clientState.map,
);
final expriedQuotes = memoizedExpiredQuotes(
state.quoteState.map,
state.clientState.map,
);
final selectedIds =
state.dashboardUIState.selectedEntities[EntityType.quote];
return _DashboardSidebar(
entityType: EntityType.quote,
label1: localization.upcomingQuotes +
(upcomingQuotes.isNotEmpty ? ' (${upcomingQuotes.length})' : ''),
list1: upcomingQuotes.isEmpty
? null
: ScrollableListViewBuilder(
itemCount: upcomingQuotes.length,
itemBuilder: (BuildContext context, int index) {
return QuoteListItem(
quote: upcomingQuotes[index],
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
label2: localization.expiredQuotes +
(expriedQuotes.isNotEmpty ? ' (${expriedQuotes.length})' : ''),
list2: expriedQuotes.isEmpty
? null
: ScrollableListViewBuilder(
itemCount: expriedQuotes.length,
itemBuilder: (BuildContext context, int index) {
return QuoteListItem(
quote: expriedQuotes[index],
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
label3: (selectedIds ?? <String>[]).isEmpty
? null
: localization.selectedQuotes + ' (${selectedIds.length})',
list3: (selectedIds ?? <String>[]).isEmpty
? null
: ScrollableListViewBuilder(
itemCount: selectedIds?.length,
itemBuilder: (BuildContext context, int index) {
final quote = state.quoteState.map[selectedIds[index]];
return quote == null
? SizedBox()
: QuoteListItem(
quote: quote,
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
);
}
}
class TaskSidebar extends StatelessWidget {
const TaskSidebar();
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final runningTasks = memoizedRunningTasks(
state.taskState.map,
state.clientState.map,
);
final recentTasks = memoizedRecentTasks(
state.taskState.map,
state.clientState.map,
);
final selectedIds =
state.dashboardUIState.selectedEntities[EntityType.task];
return _DashboardSidebar(
entityType: EntityType.quote,
label1: localization.runningTasks +
(runningTasks.isNotEmpty ? ' (${runningTasks.length})' : ''),
list1: runningTasks.isEmpty
? null
: ScrollableListViewBuilder(
itemCount: runningTasks.length,
itemBuilder: (BuildContext context, int index) {
return TaskListItem(
task: runningTasks[index],
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
label2: localization.recentTasks +
(recentTasks.isNotEmpty ? ' (${recentTasks.length})' : ''),
list2: recentTasks.isEmpty
? null
: ScrollableListViewBuilder(
itemCount: recentTasks.length,
itemBuilder: (BuildContext context, int index) {
return TaskListItem(
task: recentTasks[index],
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
label3: (selectedIds ?? <String>[]).isEmpty
? null
: localization.selectedTasks + ' (${selectedIds.length})',
list3: (selectedIds ?? <String>[]).isEmpty
? null
: ScrollableListViewBuilder(
itemCount: selectedIds?.length,
itemBuilder: (BuildContext context, int index) {
final task = state.taskState.map[selectedIds[index]];
return task == null
? SizedBox()
: TaskListItem(
task: task,
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
);
}
}
class ExpenseSidbar extends StatelessWidget {
const ExpenseSidbar();
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
final store = StoreProvider.of<AppState>(context);
final state = store.state;
final recentExpenses = memoizedRecentExpenses(
state.expenseState.map,
state.clientState.map,
);
/*
final upcomingExpenses = memoizedUpcomingExpenses(
state.expenseState.map,
state.clientState.map,
);
*/
final selectedIds =
state.dashboardUIState.selectedEntities[EntityType.expense];
return _DashboardSidebar(
entityType: EntityType.expense,
/*
label1: localization.upcomingExpenses +
(upcomingExpenses.isNotEmpty ? ' (${upcomingExpenses.length})' : ''),
list1: upcomingExpenses.isEmpty
? null
: ScrollableListViewBuilder(
itemCount: upcomingExpenses.length,
itemBuilder: (BuildContext context, int index) {
return ExpenseListItem(
expense: upcomingExpenses[index],
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
*/
label1: localization.recentExpenses +
(recentExpenses.isNotEmpty ? ' (${recentExpenses.length})' : ''),
list1: recentExpenses.isEmpty
? null
: ScrollableListViewBuilder(
itemCount: recentExpenses.length,
itemBuilder: (BuildContext context, int index) {
return ExpenseListItem(
expense: recentExpenses[index],
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
label3: (selectedIds ?? <String>[]).isEmpty
? null
: localization.selectedExpenses + ' (${selectedIds.length})',
list3: (selectedIds ?? <String>[]).isEmpty
? null
: ScrollableListViewBuilder(
itemCount: selectedIds?.length,
itemBuilder: (BuildContext context, int index) {
final expense = state.expenseState.map[selectedIds[index]];
return expense == null
? SizedBox()
: ExpenseListItem(
expense: expense,
showCheckbox: false,
);
},
separatorBuilder: (context, index) => ListDivider(),
),
);
}
}
class _DashboardSidebar extends StatelessWidget {
const _DashboardSidebar({
@required this.entityType,
@required this.label1,
@required this.list1,
this.label2,
this.list2,
this.label3,
this.list3,
});
final EntityType entityType;
final String label1;
final String label2;
final String label3;
final ScrollableListViewBuilder list1;
final ScrollableListViewBuilder list2;
final ScrollableListViewBuilder list3;
@override
Widget build(BuildContext context) {
final localization = AppLocalization.of(context);
final textTheme = Theme.of(context).textTheme;
final store = StoreProvider.of<AppState>(context);
return Container(
color: Theme.of(context).cardColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Material(
elevation: 4,
color: Theme.of(context).scaffoldBackgroundColor,
child: Container(
child: Text(label1, style: textTheme.bodyText2),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
width: double.infinity,
),
),
Expanded(
child: list1 == null
? HelpText(localization.noRecordsFound)
: ClipRRect(child: list1),
),
if (label2 != null) ...[
Material(
elevation: 4,
color: Theme.of(context).scaffoldBackgroundColor,
child: Container(
child: Text(label2, style: textTheme.bodyText2),
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
width: double.infinity,
),
),
Expanded(
child: list2 == null
? HelpText(localization.noRecordsFound)
: ClipRRect(child: list2),
),
],
AnimatedContainer(
height: label3 == null
? 0
: (MediaQuery.of(context).size.height - 100) / 2,
duration: Duration(milliseconds: kDefaultAnimationDuration),
curve: Curves.easeInOutCubic,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Material(
elevation: 4,
color: Theme.of(context).scaffoldBackgroundColor,
child: Container(
child: Row(
children: [
Expanded(
child:
Text(label3 ?? '', style: textTheme.bodyText2)),
IconButton(
visualDensity: VisualDensity.compact,
icon: Icon(Icons.clear),
onPressed: () {
store.dispatch(UpdateDashboardSelection(
entityIds: null,
entityType: entityType,
));
},
)
],
),
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
width: double.infinity,
),
),
Expanded(
child: ClipRRect(child: list3 ?? SizedBox()),
),
],
),
),
],
),
);
}
}