Browse Source

Merge pull request #81 from jld3103/cleanup/listviews

neon: Cleanup listviews
pull/83/head
jld3103 2 years ago committed by GitHub
parent
commit
7ed481a4f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 264
      packages/neon/lib/src/apps/files/widgets/browser_view.dart
  2. 1
      packages/neon/lib/src/apps/news/app.dart
  3. 76
      packages/neon/lib/src/apps/news/widgets/articles_view.dart
  4. 74
      packages/neon/lib/src/apps/news/widgets/feeds_view.dart
  5. 78
      packages/neon/lib/src/apps/news/widgets/folders_view.dart
  6. 1
      packages/neon/lib/src/apps/notes/app.dart
  7. 70
      packages/neon/lib/src/apps/notes/widgets/categories_view.dart
  8. 81
      packages/neon/lib/src/apps/notes/widgets/notes_view.dart
  9. 1
      packages/neon/lib/src/apps/notifications/app.dart
  10. 40
      packages/neon/lib/src/apps/notifications/pages/main.dart
  11. 6
      packages/neon/lib/src/utils/sort_box_builder.dart
  12. 60
      packages/neon/lib/src/widgets/custom_listview.dart

264
packages/neon/lib/src/apps/files/widgets/browser_view.dart

@ -80,157 +80,133 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
child: const Icon(Icons.add),
)
: null,
body: RefreshIndicator(
body: CustomListView<Widget>(
scrollKey: 'files-${pathSnapshot.data!.join('/')}',
withFloatingActionButton: true,
items: [
for (final uploadTask in filesData == null
? <UploadTask>[]
: uploadTasksSnapshot.data!.where(
(final task) => filesData
.where((final file) => _pathMatchesFile(task.path, file.name))
.isEmpty,
)) ...[
StreamBuilder<int>(
stream: uploadTask.progress,
builder: (final context, final uploadTaskProgressSnapshot) =>
!uploadTaskProgressSnapshot.hasData
? Container()
: _buildFile(
context: context,
details: FileDetails(
path: uploadTask.path,
isDirectory: false,
size: uploadTask.size,
etag: null,
mimeType: null,
lastModified: uploadTask.lastModified,
hasPreview: null,
isFavorite: null,
),
uploadProgress: uploadTaskProgressSnapshot.data!,
downloadProgress: null,
),
),
],
if (filesData != null) ...[
for (final file in filesData) ...[
if (!widget.onlyShowDirectories || file.isDirectory) ...[
Builder(
builder: (final context) {
final matchingUploadTasks = uploadTasksSnapshot.data!
.where((final task) => _pathMatchesFile(task.path, file.name));
final matchingDownloadTasks = downloadTasksSnapshot.data!
.where((final task) => _pathMatchesFile(task.path, file.name));
return StreamBuilder<int?>(
stream: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.progress
: Stream.value(null),
builder: (final context, final uploadTaskProgressSnapshot) =>
StreamBuilder<int?>(
stream: matchingDownloadTasks.isNotEmpty
? matchingDownloadTasks.first.progress
: Stream.value(null),
builder: (final context, final downloadTaskProgressSnapshot) => _buildFile(
context: context,
details: FileDetails(
path: [...widget.bloc.path.value, file.name],
isDirectory: matchingUploadTasks.isEmpty && file.isDirectory,
size: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.size
: file.size!,
etag: matchingUploadTasks.isNotEmpty ? null : file.etag,
mimeType: matchingUploadTasks.isNotEmpty ? null : file.mimeType,
lastModified: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.lastModified
: file.lastModified!,
hasPreview: matchingUploadTasks.isNotEmpty ? null : file.hasPreview,
isFavorite: matchingUploadTasks.isNotEmpty ? null : file.favorite,
),
uploadProgress: uploadTaskProgressSnapshot.data,
downloadProgress: downloadTaskProgressSnapshot.data,
),
),
);
},
),
],
],
],
],
isLoading: filesLoading,
error: filesError,
onRetry: () {
widget.bloc.refresh();
},
onRefresh: () async {
widget.bloc.refresh();
},
child: Column(
children: [
ExceptionWidget(
filesError,
onRetry: () {
widget.bloc.refresh();
},
),
CustomLinearProgressIndicator(
visible: filesLoading,
),
Align(
alignment: Alignment.topLeft,
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 10,
),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: <Widget>[
SizedBox(
height: 40,
child: InkWell(
onTap: () {
widget.bloc.setPath([]);
},
child: const Icon(Icons.house),
),
builder: (final context, final widget) => widget,
topScrollingChildren: [
Align(
alignment: Alignment.topLeft,
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 10,
),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: <Widget>[
SizedBox(
height: 40,
child: InkWell(
onTap: () {
widget.bloc.setPath([]);
},
child: const Icon(Icons.house),
),
),
for (var i = 0; i < pathSnapshot.data!.length; i++) ...[
InkWell(
onTap: () {
widget.bloc.setPath(pathSnapshot.data!.sublist(0, i + 1));
},
child: Text(pathSnapshot.data![i]),
),
for (var i = 0; i < pathSnapshot.data!.length; i++) ...[
InkWell(
onTap: () {
widget.bloc.setPath(pathSnapshot.data!.sublist(0, i + 1));
},
child: Text(pathSnapshot.data![i]),
],
]
.intersperse(
const Icon(
Icons.keyboard_arrow_right,
size: 40,
),
],
]
.intersperse(
const Icon(
Icons.keyboard_arrow_right,
size: 40,
),
)
.toList(),
),
)
.toList(),
),
),
if (filesData != null) ...[
Builder(
builder: (final context) {
final uploadTasksWithoutExistingFile = uploadTasksSnapshot.data!.where(
(final task) => filesData
.where((final file) => _pathMatchesFile(task.path, file.name))
.isEmpty,
);
final widgets = [
for (final uploadTask in uploadTasksWithoutExistingFile) ...[
StreamBuilder<int>(
stream: uploadTask.progress,
builder: (final context, final uploadTaskProgressSnapshot) =>
!uploadTaskProgressSnapshot.hasData
? Container()
: _buildFile(
context: context,
details: FileDetails(
path: uploadTask.path,
isDirectory: false,
size: uploadTask.size,
etag: null,
mimeType: null,
lastModified: uploadTask.lastModified,
hasPreview: null,
isFavorite: null,
),
uploadProgress: uploadTaskProgressSnapshot.data!,
downloadProgress: null,
),
),
],
for (final file in filesData) ...[
if (!widget.onlyShowDirectories || file.isDirectory) ...[
Builder(
builder: (final context) {
final matchingUploadTasks = uploadTasksSnapshot.data!
.where((final task) => _pathMatchesFile(task.path, file.name));
final matchingDownloadTasks = downloadTasksSnapshot.data!
.where((final task) => _pathMatchesFile(task.path, file.name));
return StreamBuilder<int?>(
stream: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.progress
: Stream.value(null),
builder: (final context, final uploadTaskProgressSnapshot) =>
StreamBuilder<int?>(
stream: matchingDownloadTasks.isNotEmpty
? matchingDownloadTasks.first.progress
: Stream.value(null),
builder: (final context, final downloadTaskProgressSnapshot) =>
_buildFile(
context: context,
details: FileDetails(
path: [...widget.bloc.path.value, file.name],
isDirectory: matchingUploadTasks.isEmpty && file.isDirectory,
size: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.size
: file.size!,
etag: matchingUploadTasks.isNotEmpty ? null : file.etag,
mimeType: matchingUploadTasks.isNotEmpty ? null : file.mimeType,
lastModified: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.lastModified
: file.lastModified!,
hasPreview:
matchingUploadTasks.isNotEmpty ? null : file.hasPreview,
isFavorite:
matchingUploadTasks.isNotEmpty ? null : file.favorite,
),
uploadProgress: uploadTaskProgressSnapshot.data,
downloadProgress: downloadTaskProgressSnapshot.data,
),
),
);
},
),
],
],
];
return Expanded(
child: CustomListView<Widget>(
scrollKey: 'files-${pathSnapshot.data!.join('/')}',
withFloatingActionButton: true,
items: widgets,
builder: (final context, final widget) => widget,
),
);
},
),
],
]
.intersperse(
const SizedBox(
height: 10,
),
)
.toList(),
),
),
],
),
),
),

1
packages/neon/lib/src/apps/news/app.dart

@ -8,7 +8,6 @@ import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_rx_bloc/flutter_rx_bloc.dart';
import 'package:html/dom.dart' as html_dom;
import 'package:html/parser.dart' as html_parser;
import 'package:intersperse/intersperse.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/apps/news/blocs/articles.dart';

76
packages/neon/lib/src/apps/news/widgets/articles_view.dart

@ -45,26 +45,34 @@ class _NewsArticlesViewState extends State<NewsArticlesView> {
) =>
Scaffold(
resizeToAvoidBottomInset: false,
body: RefreshIndicator(
onRefresh: () async {
widget.bloc.refresh();
},
child: Column(
children: <Widget>[
ExceptionWidget(
articlesError ?? feedsError,
onRetry: () {
if (articlesError != null) {
widget.bloc.refresh();
}
if (feedsError != null) {
widget.bloc.refreshNewsBloc();
}
},
),
CustomLinearProgressIndicator(
visible: articlesLoading || feedsLoading,
),
body: SortBoxBuilder<ArticlesSortProperty, NewsArticle>(
sortBox: articlesSortBox,
sortPropertyOption: widget.bloc.newsBloc.options.articlesSortPropertyOption,
sortBoxOrderOption: widget.bloc.newsBloc.options.articlesSortBoxOrderOption,
input: articlesData,
builder: (final context, final sorted) => CustomListView<NewsArticle>(
scrollKey: 'news-articles',
items: feedsData == null ? null : sorted,
isLoading: articlesLoading || feedsLoading,
error: articlesError ?? feedsError,
onRetry: () {
if (articlesError != null) {
widget.bloc.refresh();
}
if (feedsError != null) {
widget.bloc.refreshNewsBloc();
}
},
onRefresh: () async {
widget.bloc.refresh();
},
builder: (final context, final article) => _buildArticle(
context,
widget.bloc,
article,
feedsData!.singleWhere((final feed) => feed.id == article.feedId),
),
topFixedChildren: [
RxBlocBuilder<NewsArticlesBloc, FilterType>(
bloc: widget.bloc,
state: (final bloc) => bloc.filterType,
@ -112,33 +120,7 @@ class _NewsArticlesViewState extends State<NewsArticlesView> {
),
),
),
if (articlesData != null && feedsData != null) ...[
Expanded(
child: SortBoxBuilder<ArticlesSortProperty, NewsArticle>(
sortBox: articlesSortBox,
sortPropertyOption: widget.bloc.newsBloc.options.articlesSortPropertyOption,
sortBoxOrderOption: widget.bloc.newsBloc.options.articlesSortBoxOrderOption,
input: articlesData,
builder: (final context, final sorted) => CustomListView<NewsArticle>(
scrollKey: 'news-articles',
items: sorted,
builder: (final context, final article) => _buildArticle(
context,
widget.bloc,
article,
feedsData.singleWhere((final feed) => feed.id == article.feedId),
),
),
),
),
],
]
.intersperse(
const SizedBox(
height: 10,
),
)
.toList(),
],
),
),
),

74
packages/neon/lib/src/apps/news/widgets/feeds_view.dart

@ -48,52 +48,34 @@ class NewsFeedsView extends StatelessWidget {
},
child: const Icon(Icons.add),
),
body: RefreshIndicator(
onRefresh: () async {
bloc.refresh(
mainArticlesToo: true,
);
},
child: Column(
children: <Widget>[
ExceptionWidget(
feedsError ?? foldersError,
onRetry: () {
bloc.refresh(
mainArticlesToo: false,
);
},
),
CustomLinearProgressIndicator(
visible: feedsLoading || foldersLoading,
),
if (feedsData != null && foldersData != null) ...[
Expanded(
child: SortBoxBuilder<FeedsSortProperty, NewsFeed>(
sortBox: feedsSortBox,
sortPropertyOption: bloc.options.feedsSortPropertyOption,
sortBoxOrderOption: bloc.options.feedsSortBoxOrderOption,
input: feedsData.where((final f) => folderID == null || f.folderId == folderID).toList(),
builder: (final context, final sorted) => CustomListView<NewsFeed>(
scrollKey: 'news-feeds',
withFloatingActionButton: true,
items: sorted,
builder: (final context, final feed) => _buildFeed(
context,
feed,
foldersData,
),
),
),
),
],
]
.intersperse(
const SizedBox(
height: 10,
),
)
.toList(),
body: SortBoxBuilder<FeedsSortProperty, NewsFeed>(
sortBox: feedsSortBox,
sortPropertyOption: bloc.options.feedsSortPropertyOption,
sortBoxOrderOption: bloc.options.feedsSortBoxOrderOption,
input: foldersData == null
? null
: feedsData?.where((final f) => folderID == null || f.folderId == folderID).toList(),
builder: (final context, final sorted) => CustomListView<NewsFeed>(
scrollKey: 'news-feeds',
withFloatingActionButton: true,
items: sorted,
isLoading: feedsLoading || foldersLoading,
error: feedsError ?? foldersError,
onRetry: () {
bloc.refresh(
mainArticlesToo: false,
);
},
onRefresh: () async {
bloc.refresh(
mainArticlesToo: true,
);
},
builder: (final context, final feed) => _buildFeed(
context,
feed,
foldersData!,
),
),
),
),

78
packages/neon/lib/src/apps/news/widgets/folders_view.dart

@ -43,55 +43,37 @@ class NewsFoldersView extends StatelessWidget {
final feedsLoading,
final _,
) =>
RefreshIndicator(
onRefresh: () async {
bloc.refresh(
mainArticlesToo: true,
);
},
child: Column(
children: <Widget>[
ExceptionWidget(
feedsError ?? foldersError,
onRetry: () {
bloc.refresh(
mainArticlesToo: false,
);
},
),
CustomLinearProgressIndicator(
visible: feedsLoading || foldersLoading,
),
if (feedsData != null && foldersData != null) ...[
Expanded(
child: SortBoxBuilder<FoldersSortProperty, FolderFeedsWrapper>(
sortBox: foldersSortBox,
sortPropertyOption: bloc.options.foldersSortPropertyOption,
sortBoxOrderOption: bloc.options.foldersSortBoxOrderOption,
input: foldersData
.map(
(final folder) => FolderFeedsWrapper(
folder,
feedsData.where((final feed) => feed.folderId == folder.id).toList(),
),
)
.toList(),
builder: (final context, final sorted) => CustomListView<FolderFeedsWrapper>(
scrollKey: 'news-folders',
withFloatingActionButton: true,
items: sorted,
builder: _buildFolder,
SortBoxBuilder<FoldersSortProperty, FolderFeedsWrapper>(
sortBox: foldersSortBox,
sortPropertyOption: bloc.options.foldersSortPropertyOption,
sortBoxOrderOption: bloc.options.foldersSortBoxOrderOption,
input: feedsData == null
? null
: foldersData
?.map(
(final folder) => FolderFeedsWrapper(
folder,
feedsData.where((final feed) => feed.folderId == folder.id).toList(),
),
),
),
],
]
.intersperse(
const SizedBox(
height: 10,
),
)
.toList(),
)
.toList(),
builder: (final context, final sorted) => CustomListView<FolderFeedsWrapper>(
scrollKey: 'news-folders',
withFloatingActionButton: true,
items: sorted,
isLoading: feedsLoading || foldersLoading,
error: feedsError ?? foldersError,
onRetry: () {
bloc.refresh(
mainArticlesToo: false,
);
},
onRefresh: () async {
bloc.refresh(
mainArticlesToo: true,
);
},
builder: _buildFolder,
),
),
),

1
packages/neon/lib/src/apps/notes/app.dart

@ -5,7 +5,6 @@ import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:intersperse/intersperse.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/apps/notes/blocs/notes.dart';

70
packages/neon/lib/src/apps/notes/widgets/categories_view.dart

@ -19,52 +19,32 @@ class NotesCategoriesView extends StatelessWidget {
final notesLoading,
final _,
) =>
RefreshIndicator(
onRefresh: () async {
bloc.refresh();
},
child: Column(
children: [
ExceptionWidget(
notesError,
onRetry: () {
bloc.refresh();
},
),
CustomLinearProgressIndicator(
visible: notesLoading,
),
if (notesData != null) ...[
Expanded(
child: SortBoxBuilder<CategoriesSortProperty, NoteCategory>(
sortBox: categoriesSortBox,
sortPropertyOption: bloc.options.categoriesSortPropertyOption,
sortBoxOrderOption: bloc.options.categoriesSortBoxOrderOption,
input: notesData
.map((final note) => note.category!)
.toSet()
.map(
(final category) => NoteCategory(
category,
notesData.where((final note) => note.category == category).length,
),
)
.toList(),
builder: (final context, final sorted) => CustomListView<NoteCategory>(
scrollKey: 'notes-categories',
items: sorted,
builder: _buildCategory,
),
),
SortBoxBuilder<CategoriesSortProperty, NoteCategory>(
sortBox: categoriesSortBox,
sortPropertyOption: bloc.options.categoriesSortPropertyOption,
sortBoxOrderOption: bloc.options.categoriesSortBoxOrderOption,
input: notesData
?.map((final note) => note.category!)
.toSet()
.map(
(final category) => NoteCategory(
category,
notesData.where((final note) => note.category == category).length,
),
],
]
.intersperse(
const SizedBox(
height: 10,
),
)
.toList(),
)
.toList(),
builder: (final context, final sorted) => CustomListView<NoteCategory>(
scrollKey: 'notes-categories',
items: sorted,
isLoading: notesLoading,
error: notesError,
onRetry: () {
bloc.refresh();
},
onRefresh: () async {
bloc.refresh();
},
builder: _buildCategory,
),
),
);

81
packages/neon/lib/src/apps/notes/widgets/notes_view.dart

@ -41,54 +41,41 @@ class NotesView extends StatelessWidget {
},
child: const Icon(Icons.add),
),
body: RefreshIndicator(
onRefresh: () async {
bloc.refresh();
},
child: Column(
children: [
ExceptionWidget(
notesError,
onRetry: () {
bloc.refresh();
},
),
CustomLinearProgressIndicator(
visible: notesLoading,
),
if (notesData != null) ...[
Expanded(
child: SortBoxBuilder<NotesSortProperty, NotesNote>(
sortBox: notesSortBox,
sortPropertyOption: bloc.options.notesSortPropertyOption,
sortBoxOrderOption: bloc.options.notesSortBoxOrderOption,
input: category != null
? notesData.where((final note) => note.favorite! && note.category == category).toList()
: notesData.where((final note) => note.favorite!).toList(),
builder: (final context, final sortedFavorites) => SortBoxBuilder<NotesSortProperty, NotesNote>(
sortBox: notesSortBox,
sortPropertyOption: bloc.options.notesSortPropertyOption,
sortBoxOrderOption: bloc.options.notesSortBoxOrderOption,
input: category != null
? notesData.where((final note) => !note.favorite! && note.category == category).toList()
: notesData.where((final note) => !note.favorite!).toList(),
builder: (final context, final sortedNonFavorites) => CustomListView<NotesNote>(
scrollKey: 'notes-notes',
withFloatingActionButton: true,
items: [...sortedFavorites, ...sortedNonFavorites],
builder: _buildNote,
),
),
),
),
body: SortBoxBuilder<NotesSortProperty, NotesNote>(
sortBox: notesSortBox,
sortPropertyOption: bloc.options.notesSortPropertyOption,
sortBoxOrderOption: bloc.options.notesSortBoxOrderOption,
input: category != null
? notesData?.where((final note) => note.favorite! && note.category == category).toList()
: notesData?.where((final note) => note.favorite!).toList(),
builder: (final context, final sortedFavorites) => SortBoxBuilder<NotesSortProperty, NotesNote>(
sortBox: notesSortBox,
sortPropertyOption: bloc.options.notesSortPropertyOption,
sortBoxOrderOption: bloc.options.notesSortBoxOrderOption,
input: category != null
? notesData?.where((final note) => !note.favorite! && note.category == category).toList()
: notesData?.where((final note) => !note.favorite!).toList(),
builder: (final context, final sortedNonFavorites) => CustomListView<NotesNote>(
scrollKey: 'notes-notes',
withFloatingActionButton: true,
items: [
if (sortedFavorites != null) ...[
...sortedFavorites,
],
if (sortedNonFavorites != null) ...[
...sortedNonFavorites,
],
],
]
.intersperse(
const SizedBox(
height: 10,
),
)
.toList(),
isLoading: notesLoading,
error: notesError,
onRetry: () {
bloc.refresh();
},
onRefresh: () async {
bloc.refresh();
},
builder: _buildNote,
),
),
),
),

1
packages/neon/lib/src/apps/notifications/app.dart

@ -1,7 +1,6 @@
library notifications;
import 'package:flutter/material.dart';
import 'package:intersperse/intersperse.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/apps/notifications/blocs/notifications.dart';

40
packages/neon/lib/src/apps/notifications/pages/main.dart

@ -42,39 +42,19 @@ class _NotificationsMainPageState extends State<NotificationsMainPage> {
},
child: const Icon(MdiIcons.checkAll),
),
body: RefreshIndicator(
body: CustomListView<NotificationsNotification>(
scrollKey: 'notifications-notifications',
withFloatingActionButton: true,
items: notificationsData,
isLoading: notificationsLoading,
error: notificationsError,
onRetry: () {
widget.bloc.refresh();
},
onRefresh: () async {
widget.bloc.refresh();
},
child: Column(
children: [
ExceptionWidget(
notificationsError,
onRetry: () {
widget.bloc.refresh();
},
),
CustomLinearProgressIndicator(
visible: notificationsLoading,
),
if (notificationsData != null) ...[
Expanded(
child: CustomListView<NotificationsNotification>(
scrollKey: 'notifications-notifications',
withFloatingActionButton: true,
items: notificationsData,
builder: _buildNotification,
),
),
],
]
.intersperse(
const SizedBox(
height: 10,
),
)
.toList(),
),
builder: _buildNotification,
),
),
);

6
packages/neon/lib/src/utils/sort_box_builder.dart

@ -13,8 +13,8 @@ class SortBoxBuilder<T extends Enum, R> extends StatelessWidget {
final SortBox<T, R> sortBox;
final SelectOption<T> sortPropertyOption;
final SelectOption<SortBoxOrder> sortBoxOrderOption;
final List<R> input;
final Widget Function(BuildContext, List<R>) builder;
final List<R>? input;
final Widget Function(BuildContext, List<R>?) builder;
@override
Widget build(final BuildContext context) => OptionBuilder<T>(
@ -25,7 +25,7 @@ class SortBoxBuilder<T extends Enum, R> extends StatelessWidget {
? Container()
: builder(
context,
sortBox.sort(input, Box(property, order)),
input == null ? null : sortBox.sort(input!, Box(property, order)),
),
),
);

60
packages/neon/lib/src/widgets/custom_listview.dart

@ -3,27 +3,67 @@ part of '../neon.dart';
class CustomListView<T> extends StatelessWidget {
const CustomListView({
required this.items,
required this.isLoading,
required this.error,
required this.onRetry,
required this.onRefresh,
required this.builder,
this.scrollKey,
this.withFloatingActionButton = false,
this.topFixedChildren,
this.topScrollingChildren,
super.key,
});
final List<T> items;
final List<T>? items;
final bool isLoading;
final dynamic error;
final Function() onRetry;
final Future Function() onRefresh;
final Widget Function(BuildContext, T data) builder;
final String? scrollKey;
final bool withFloatingActionButton;
final List<Widget>? topFixedChildren;
final List<Widget>? topScrollingChildren;
@override
Widget build(final BuildContext context) => Scrollbar(
child: ListView.separated(
key: scrollKey != null ? PageStorageKey<String>(scrollKey!) : null,
padding: withFloatingActionButton ? const EdgeInsets.only(bottom: 88) : null,
separatorBuilder: (final context, final index) => const SizedBox(
height: 10,
),
itemCount: items.length,
itemBuilder: (final context, final index) => builder(context, items[index]),
Widget build(final BuildContext context) => RefreshIndicator(
onRefresh: onRefresh,
child: Column(
children: [
if (topFixedChildren != null) ...[
...topFixedChildren!,
],
CustomLinearProgressIndicator(
margin: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
visible: isLoading,
),
Expanded(
child: Scrollbar(
child: ListView(
key: scrollKey != null ? PageStorageKey<String>(scrollKey!) : null,
padding: withFloatingActionButton ? const EdgeInsets.only(bottom: 88) : null,
children: [
if (topScrollingChildren != null) ...[
...topScrollingChildren!,
],
ExceptionWidget(
error,
onRetry: onRetry,
),
if (items != null) ...[
for (final item in items!) ...[
builder(context, item),
],
],
],
),
),
),
],
),
);
}

Loading…
Cancel
Save