Add scrollbars

This commit is contained in:
Hillel Coren 2021-03-09 21:26:55 +02:00
parent ea13825026
commit 17cfc3d478
7 changed files with 63 additions and 75 deletions

View File

@ -56,11 +56,13 @@ class ScrollableListViewBuilder extends StatefulWidget {
Key key, Key key,
@required this.itemBuilder, @required this.itemBuilder,
@required this.itemCount, @required this.itemCount,
this.separatorBuilder,
this.scrollController, this.scrollController,
this.padding, this.padding,
}) : super(key: key); }) : super(key: key);
final IndexedWidgetBuilder itemBuilder; final IndexedWidgetBuilder itemBuilder;
final IndexedWidgetBuilder separatorBuilder;
final int itemCount; final int itemCount;
final ScrollController scrollController; final ScrollController scrollController;
final EdgeInsetsGeometry padding; final EdgeInsetsGeometry padding;
@ -92,7 +94,16 @@ class _ScrollableListViewBuilderState extends State<ScrollableListViewBuilder> {
onEnter: (event) => setState(() => _isHovered = true), onEnter: (event) => setState(() => _isHovered = true),
onExit: (event) => setState(() => _isHovered = false), onExit: (event) => setState(() => _isHovered = false),
child: Scrollbar( child: Scrollbar(
child: ListView.builder( child: widget.separatorBuilder != null
? ListView.separated(
separatorBuilder: widget.separatorBuilder,
padding: widget.padding,
itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount,
controller: widget.scrollController ?? _scrollController,
shrinkWrap: true,
)
: ListView.builder(
padding: widget.padding, padding: widget.padding,
itemBuilder: widget.itemBuilder, itemBuilder: widget.itemBuilder,
itemCount: widget.itemCount, itemCount: widget.itemCount,

View File

@ -17,6 +17,7 @@ import 'package:invoiceninja_flutter/ui/app/lists/list_divider.dart';
import 'package:invoiceninja_flutter/ui/app/lists/list_filter.dart'; import 'package:invoiceninja_flutter/ui/app/lists/list_filter.dart';
import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart'; import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart'; import 'package:invoiceninja_flutter/ui/app/presenters/entity_presenter.dart';
import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
import 'package:invoiceninja_flutter/ui/app/tables/app_data_table.dart'; import 'package:invoiceninja_flutter/ui/app/tables/app_data_table.dart';
import 'package:invoiceninja_flutter/ui/app/tables/app_paginated_data_table.dart'; import 'package:invoiceninja_flutter/ui/app/tables/app_paginated_data_table.dart';
import 'package:invoiceninja_flutter/ui/app/tables/entity_datatable.dart'; import 'package:invoiceninja_flutter/ui/app/tables/entity_datatable.dart';
@ -54,14 +55,11 @@ class EntityList extends StatefulWidget {
class _EntityListState extends State<EntityList> { class _EntityListState extends State<EntityList> {
EntityDataTableSource dataTableSource; EntityDataTableSource dataTableSource;
ScrollController _scrollController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_scrollController = ScrollController();
final entityType = widget.entityType; final entityType = widget.entityType;
final state = widget.state; final state = widget.state;
final entityList = widget.entityList; final entityList = widget.entityList;
@ -95,12 +93,6 @@ class _EntityListState extends State<EntityList> {
dataTableSource.notifyListeners(); dataTableSource.notifyListeners();
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final store = StoreProvider.of<AppState>(context); final store = StoreProvider.of<AppState>(context);
@ -157,11 +149,8 @@ class _EntityListState extends State<EntityList> {
fit: FlexFit.loose, fit: FlexFit.loose,
child: entityList.isEmpty child: entityList.isEmpty
? HelpText(AppLocalization.of(context).noRecordsFound) ? HelpText(AppLocalization.of(context).noRecordsFound)
: AppScrollbar( : ScrollableListViewBuilder(
controller: _scrollController,
child: ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 25), padding: const EdgeInsets.symmetric(vertical: 25),
controller: _scrollController,
separatorBuilder: (context, index) => separatorBuilder: (context, index) =>
(index == 0 || index == entityList.length) (index == 0 || index == entityList.length)
? SizedBox() ? SizedBox()
@ -177,12 +166,11 @@ class _EntityListState extends State<EntityList> {
return widget.itemBuilder(context, index - 1); return widget.itemBuilder(context, index - 1);
} }
}, },
),
) /*DraggableScrollbar.semicircle( ) /*DraggableScrollbar.semicircle(
backgroundColor: Theme.of(context).backgroundColor, backgroundColor: Theme.of(context).backgroundColor,
scrollbarTimeToFade: Duration(seconds: 1), scrollbarTimeToFade: Duration(seconds: 1),
controller: _scrollController, controller: _scrollController,
child: ListView.separated( child: ScrollableListViewBuilder(
padding: const EdgeInsets.symmetric(vertical: 25), padding: const EdgeInsets.symmetric(vertical: 25),
controller: _scrollController, controller: _scrollController,
separatorBuilder: (context, index) => separatorBuilder: (context, index) =>

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:invoiceninja_flutter/ui/app/lists/activity_list_tile.dart'; import 'package:invoiceninja_flutter/ui/app/lists/activity_list_tile.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/app/loading_indicator.dart'; import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
import 'package:invoiceninja_flutter/ui/client/view/client_view_vm.dart'; import 'package:invoiceninja_flutter/ui/client/view/client_view_vm.dart';
class ClientViewActivity extends StatefulWidget { class ClientViewActivity extends StatefulWidget {
@ -31,7 +32,7 @@ class _ClientViewActivityState extends State<ClientViewActivity> {
return LoadingIndicator(); return LoadingIndicator();
} }
return ListView.separated( return ScrollableListViewBuilder(
itemCount: activities.length, itemCount: activities.length,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
separatorBuilder: (context, index) => ListDivider(), separatorBuilder: (context, index) => ListDivider(),

View File

@ -7,6 +7,7 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart';
import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart'; import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.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/app/loading_indicator.dart'; import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
import 'package:invoiceninja_flutter/ui/client/view/client_view_vm.dart'; import 'package:invoiceninja_flutter/ui/client/view/client_view_vm.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:invoiceninja_flutter/utils/icons.dart'; import 'package:invoiceninja_flutter/utils/icons.dart';
@ -40,8 +41,7 @@ class _ClientViewLedgerState extends State<ClientViewLedger> {
return LoadingIndicator(); return LoadingIndicator();
} }
return ListView.separated( return ScrollableListViewBuilder(
shrinkWrap: true,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
itemCount: ledgers.length + 1, itemCount: ledgers.length + 1,
separatorBuilder: (context, index) => ListDivider(), separatorBuilder: (context, index) => ListDivider(),

View File

@ -7,6 +7,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/app/scrollable_listview.dart';
import 'package:invoiceninja_flutter/ui/expense/expense_list_item.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';
@ -109,8 +110,7 @@ class InvoiceSidebar extends StatelessWidget {
(upcomingInvoices.isNotEmpty ? ' (${upcomingInvoices.length})' : ''), (upcomingInvoices.isNotEmpty ? ' (${upcomingInvoices.length})' : ''),
list1: upcomingInvoices.isEmpty list1: upcomingInvoices.isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: upcomingInvoices.length, itemCount: upcomingInvoices.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return InvoiceListItem( return InvoiceListItem(
@ -124,8 +124,7 @@ class InvoiceSidebar extends StatelessWidget {
(pastDueInvoices.isNotEmpty ? ' (${pastDueInvoices.length})' : ''), (pastDueInvoices.isNotEmpty ? ' (${pastDueInvoices.length})' : ''),
list2: pastDueInvoices.isEmpty list2: pastDueInvoices.isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: pastDueInvoices.length, itemCount: pastDueInvoices.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return InvoiceListItem( return InvoiceListItem(
@ -140,8 +139,7 @@ class InvoiceSidebar extends StatelessWidget {
: localization.selectedInvoices + ' (${selectedIds.length})', : localization.selectedInvoices + ' (${selectedIds.length})',
list3: (selectedIds ?? <String>[]).isEmpty list3: (selectedIds ?? <String>[]).isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: selectedIds?.length, itemCount: selectedIds?.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final invoice = state.invoiceState.map[selectedIds[index]]; final invoice = state.invoiceState.map[selectedIds[index]];
@ -179,8 +177,7 @@ class PaymentSidebar extends StatelessWidget {
(recentPayments.isNotEmpty ? ' (${recentPayments.length})' : ''), (recentPayments.isNotEmpty ? ' (${recentPayments.length})' : ''),
list1: recentPayments.isEmpty list1: recentPayments.isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: recentPayments.length, itemCount: recentPayments.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return PaymentListItem( return PaymentListItem(
@ -195,8 +192,7 @@ class PaymentSidebar extends StatelessWidget {
: localization.selectedPayments + ' (${selectedIds.length})', : localization.selectedPayments + ' (${selectedIds.length})',
list3: (selectedIds ?? <String>[]).isEmpty list3: (selectedIds ?? <String>[]).isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: selectedIds?.length, itemCount: selectedIds?.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final payment = state.paymentState.map[selectedIds[index]]; final payment = state.paymentState.map[selectedIds[index]];
@ -238,8 +234,7 @@ class QuoteSidebar extends StatelessWidget {
(upcomingQuotes.isNotEmpty ? ' (${upcomingQuotes.length})' : ''), (upcomingQuotes.isNotEmpty ? ' (${upcomingQuotes.length})' : ''),
list1: upcomingQuotes.isEmpty list1: upcomingQuotes.isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: upcomingQuotes.length, itemCount: upcomingQuotes.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return QuoteListItem( return QuoteListItem(
@ -253,8 +248,7 @@ class QuoteSidebar extends StatelessWidget {
(expriedQuotes.isNotEmpty ? ' (${expriedQuotes.length})' : ''), (expriedQuotes.isNotEmpty ? ' (${expriedQuotes.length})' : ''),
list2: expriedQuotes.isEmpty list2: expriedQuotes.isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: expriedQuotes.length, itemCount: expriedQuotes.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return QuoteListItem( return QuoteListItem(
@ -269,8 +263,7 @@ class QuoteSidebar extends StatelessWidget {
: localization.selectedQuotes + ' (${selectedIds.length})', : localization.selectedQuotes + ' (${selectedIds.length})',
list3: (selectedIds ?? <String>[]).isEmpty list3: (selectedIds ?? <String>[]).isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: selectedIds?.length, itemCount: selectedIds?.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final quote = state.quoteState.map[selectedIds[index]]; final quote = state.quoteState.map[selectedIds[index]];
@ -312,8 +305,7 @@ class TaskSidebar extends StatelessWidget {
(runningTasks.isNotEmpty ? ' (${runningTasks.length})' : ''), (runningTasks.isNotEmpty ? ' (${runningTasks.length})' : ''),
list1: runningTasks.isEmpty list1: runningTasks.isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: runningTasks.length, itemCount: runningTasks.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return TaskListItem( return TaskListItem(
@ -327,8 +319,7 @@ class TaskSidebar extends StatelessWidget {
(recentTasks.isNotEmpty ? ' (${recentTasks.length})' : ''), (recentTasks.isNotEmpty ? ' (${recentTasks.length})' : ''),
list2: recentTasks.isEmpty list2: recentTasks.isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: recentTasks.length, itemCount: recentTasks.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return TaskListItem( return TaskListItem(
@ -343,8 +334,7 @@ class TaskSidebar extends StatelessWidget {
: localization.selectedTasks + ' (${selectedIds.length})', : localization.selectedTasks + ' (${selectedIds.length})',
list3: (selectedIds ?? <String>[]).isEmpty list3: (selectedIds ?? <String>[]).isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: selectedIds?.length, itemCount: selectedIds?.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final task = state.taskState.map[selectedIds[index]]; final task = state.taskState.map[selectedIds[index]];
@ -390,8 +380,7 @@ class ExpenseSidbar extends StatelessWidget {
(upcomingExpenses.isNotEmpty ? ' (${upcomingExpenses.length})' : ''), (upcomingExpenses.isNotEmpty ? ' (${upcomingExpenses.length})' : ''),
list1: upcomingExpenses.isEmpty list1: upcomingExpenses.isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: upcomingExpenses.length, itemCount: upcomingExpenses.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return ExpenseListItem( return ExpenseListItem(
@ -406,8 +395,7 @@ class ExpenseSidbar extends StatelessWidget {
(recentExpenses.isNotEmpty ? ' (${recentExpenses.length})' : ''), (recentExpenses.isNotEmpty ? ' (${recentExpenses.length})' : ''),
list1: recentExpenses.isEmpty list1: recentExpenses.isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: recentExpenses.length, itemCount: recentExpenses.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return ExpenseListItem( return ExpenseListItem(
@ -422,8 +410,7 @@ class ExpenseSidbar extends StatelessWidget {
: localization.selectedExpenses + ' (${selectedIds.length})', : localization.selectedExpenses + ' (${selectedIds.length})',
list3: (selectedIds ?? <String>[]).isEmpty list3: (selectedIds ?? <String>[]).isEmpty
? null ? null
: ListView.separated( : ScrollableListViewBuilder(
shrinkWrap: true,
itemCount: selectedIds?.length, itemCount: selectedIds?.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final expense = state.expenseState.map[selectedIds[index]]; final expense = state.expenseState.map[selectedIds[index]];
@ -455,9 +442,9 @@ class _DashboardSidebar extends StatelessWidget {
final String label1; final String label1;
final String label2; final String label2;
final String label3; final String label3;
final ListView list1; final ScrollableListViewBuilder list1;
final ListView list2; final ScrollableListViewBuilder list2;
final ListView list3; final ScrollableListViewBuilder list3;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.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/app/loading_indicator.dart'; import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart'; import 'package:invoiceninja_flutter/ui/invoice/view/invoice_view_vm.dart';
import 'package:invoiceninja_flutter/utils/formatting.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart';
import 'package:timeago/timeago.dart' as timeago; import 'package:timeago/timeago.dart' as timeago;
@ -37,8 +38,7 @@ class _InvoiceViewHistoryState extends State<InvoiceViewHistory> {
final historyList = invoice.history.toList(); final historyList = invoice.history.toList();
historyList.sort((a, b) => b.createdAt.compareTo(a.createdAt)); historyList.sort((a, b) => b.createdAt.compareTo(a.createdAt));
return ListView.separated( return ScrollableListViewBuilder(
shrinkWrap: true,
padding: const EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (BuildContext context, index) { itemBuilder: (BuildContext context, index) {
final history = historyList[index]; final history = historyList[index];

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.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/app/loading_indicator.dart'; import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart';
import 'package:invoiceninja_flutter/ui/app/scrollable_listview.dart';
import 'package:invoiceninja_flutter/ui/app/tables/entity_datatable.dart'; import 'package:invoiceninja_flutter/ui/app/tables/entity_datatable.dart';
import 'package:invoiceninja_flutter/ui/payment_term/payment_term_list_item.dart'; import 'package:invoiceninja_flutter/ui/payment_term/payment_term_list_item.dart';
import 'package:invoiceninja_flutter/ui/payment_term/payment_term_list_vm.dart'; import 'package:invoiceninja_flutter/ui/payment_term/payment_term_list_vm.dart';
@ -38,7 +39,7 @@ class _PaymentTermListState extends State<PaymentTermList> {
return RefreshIndicator( return RefreshIndicator(
onRefresh: () => viewModel.onRefreshed(context), onRefresh: () => viewModel.onRefreshed(context),
child: ListView.separated( child: ScrollableListViewBuilder(
separatorBuilder: (context, index) => ListDivider(), separatorBuilder: (context, index) => ListDivider(),
itemCount: viewModel.paymentTermList.length, itemCount: viewModel.paymentTermList.length,
itemBuilder: (BuildContext context, index) { itemBuilder: (BuildContext context, index) {