Browse Source

neon: Cleanup listviews

pull/81/head
jld3103 2 years ago
parent
commit
e5af3a1b4b
No known key found for this signature in database
GPG Key ID: 9062417B9E8EB7B3
  1. 126
      packages/neon/lib/src/apps/files/widgets/browser_view.dart
  2. 1
      packages/neon/lib/src/apps/news/app.dart
  3. 56
      packages/neon/lib/src/apps/news/widgets/articles_view.dart
  4. 52
      packages/neon/lib/src/apps/news/widgets/feeds_view.dart
  5. 52
      packages/neon/lib/src/apps/news/widgets/folders_view.dart
  6. 1
      packages/neon/lib/src/apps/notes/app.dart
  7. 40
      packages/neon/lib/src/apps/notes/widgets/categories_view.dart
  8. 55
      packages/neon/lib/src/apps/notes/widgets/notes_view.dart
  9. 1
      packages/neon/lib/src/apps/notifications/app.dart
  10. 36
      packages/neon/lib/src/apps/notifications/pages/main.dart
  11. 6
      packages/neon/lib/src/utils/sort_box_builder.dart
  12. 54
      packages/neon/lib/src/widgets/custom_listview.dart

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

@ -80,68 +80,17 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
child: const Icon(Icons.add), child: const Icon(Icons.add),
) )
: null, : null,
body: RefreshIndicator( body: CustomListView<Widget>(
onRefresh: () async { scrollKey: 'files-${pathSnapshot.data!.join('/')}',
widget.bloc.refresh(); withFloatingActionButton: true,
}, items: [
child: Column( for (final uploadTask in filesData == null
children: [ ? <UploadTask>[]
ExceptionWidget( : uploadTasksSnapshot.data!.where(
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),
),
),
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,
),
)
.toList(),
),
),
),
if (filesData != null) ...[
Builder(
builder: (final context) {
final uploadTasksWithoutExistingFile = uploadTasksSnapshot.data!.where(
(final task) => filesData (final task) => filesData
.where((final file) => _pathMatchesFile(task.path, file.name)) .where((final file) => _pathMatchesFile(task.path, file.name))
.isEmpty, .isEmpty,
); )) ...[
final widgets = [
for (final uploadTask in uploadTasksWithoutExistingFile) ...[
StreamBuilder<int>( StreamBuilder<int>(
stream: uploadTask.progress, stream: uploadTask.progress,
builder: (final context, final uploadTaskProgressSnapshot) => builder: (final context, final uploadTaskProgressSnapshot) =>
@ -164,6 +113,7 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
), ),
), ),
], ],
if (filesData != null) ...[
for (final file in filesData) ...[ for (final file in filesData) ...[
if (!widget.onlyShowDirectories || file.isDirectory) ...[ if (!widget.onlyShowDirectories || file.isDirectory) ...[
Builder( Builder(
@ -182,8 +132,7 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
stream: matchingDownloadTasks.isNotEmpty stream: matchingDownloadTasks.isNotEmpty
? matchingDownloadTasks.first.progress ? matchingDownloadTasks.first.progress
: Stream.value(null), : Stream.value(null),
builder: (final context, final downloadTaskProgressSnapshot) => builder: (final context, final downloadTaskProgressSnapshot) => _buildFile(
_buildFile(
context: context, context: context,
details: FileDetails( details: FileDetails(
path: [...widget.bloc.path.value, file.name], path: [...widget.bloc.path.value, file.name],
@ -196,10 +145,8 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
lastModified: matchingUploadTasks.isNotEmpty lastModified: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.lastModified ? matchingUploadTasks.first.lastModified
: file.lastModified!, : file.lastModified!,
hasPreview: hasPreview: matchingUploadTasks.isNotEmpty ? null : file.hasPreview,
matchingUploadTasks.isNotEmpty ? null : file.hasPreview, isFavorite: matchingUploadTasks.isNotEmpty ? null : file.favorite,
isFavorite:
matchingUploadTasks.isNotEmpty ? null : file.favorite,
), ),
uploadProgress: uploadTaskProgressSnapshot.data, uploadProgress: uploadTaskProgressSnapshot.data,
downloadProgress: downloadTaskProgressSnapshot.data, downloadProgress: downloadTaskProgressSnapshot.data,
@ -210,29 +157,58 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
), ),
], ],
], ],
]; ],
],
return Expanded( isLoading: filesLoading,
child: CustomListView<Widget>( error: filesError,
scrollKey: 'files-${pathSnapshot.data!.join('/')}', onRetry: () {
withFloatingActionButton: true, widget.bloc.refresh();
items: widgets, },
onRefresh: () async {
widget.bloc.refresh();
},
builder: (final context, final widget) => widget, 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]),
), ),
], ],
] ]
.intersperse( .intersperse(
const SizedBox( const Icon(
height: 10, Icons.keyboard_arrow_right,
size: 40,
), ),
) )
.toList(), .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:flutter_rx_bloc/flutter_rx_bloc.dart';
import 'package:html/dom.dart' as html_dom; import 'package:html/dom.dart' as html_dom;
import 'package:html/parser.dart' as html_parser; 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:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:neon/l10n/localizations.dart'; import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/apps/news/blocs/articles.dart'; import 'package:neon/src/apps/news/blocs/articles.dart';

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

@ -45,14 +45,16 @@ class _NewsArticlesViewState extends State<NewsArticlesView> {
) => ) =>
Scaffold( Scaffold(
resizeToAvoidBottomInset: false, resizeToAvoidBottomInset: false,
body: RefreshIndicator( body: SortBoxBuilder<ArticlesSortProperty, NewsArticle>(
onRefresh: () async { sortBox: articlesSortBox,
widget.bloc.refresh(); sortPropertyOption: widget.bloc.newsBloc.options.articlesSortPropertyOption,
}, sortBoxOrderOption: widget.bloc.newsBloc.options.articlesSortBoxOrderOption,
child: Column( input: articlesData,
children: <Widget>[ builder: (final context, final sorted) => CustomListView<NewsArticle>(
ExceptionWidget( scrollKey: 'news-articles',
articlesError ?? feedsError, items: feedsData == null ? null : sorted,
isLoading: articlesLoading || feedsLoading,
error: articlesError ?? feedsError,
onRetry: () { onRetry: () {
if (articlesError != null) { if (articlesError != null) {
widget.bloc.refresh(); widget.bloc.refresh();
@ -61,10 +63,16 @@ class _NewsArticlesViewState extends State<NewsArticlesView> {
widget.bloc.refreshNewsBloc(); 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),
), ),
CustomLinearProgressIndicator( topFixedChildren: [
visible: articlesLoading || feedsLoading,
),
RxBlocBuilder<NewsArticlesBloc, FilterType>( RxBlocBuilder<NewsArticlesBloc, FilterType>(
bloc: widget.bloc, bloc: widget.bloc,
state: (final bloc) => bloc.filterType, 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(),
), ),
), ),
), ),

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

@ -48,52 +48,34 @@ class NewsFeedsView extends StatelessWidget {
}, },
child: const Icon(Icons.add), child: const Icon(Icons.add),
), ),
body: RefreshIndicator( body: SortBoxBuilder<FeedsSortProperty, NewsFeed>(
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, sortBox: feedsSortBox,
sortPropertyOption: bloc.options.feedsSortPropertyOption, sortPropertyOption: bloc.options.feedsSortPropertyOption,
sortBoxOrderOption: bloc.options.feedsSortBoxOrderOption, sortBoxOrderOption: bloc.options.feedsSortBoxOrderOption,
input: feedsData.where((final f) => folderID == null || f.folderId == folderID).toList(), input: foldersData == null
? null
: feedsData?.where((final f) => folderID == null || f.folderId == folderID).toList(),
builder: (final context, final sorted) => CustomListView<NewsFeed>( builder: (final context, final sorted) => CustomListView<NewsFeed>(
scrollKey: 'news-feeds', scrollKey: 'news-feeds',
withFloatingActionButton: true, withFloatingActionButton: true,
items: sorted, 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( builder: (final context, final feed) => _buildFeed(
context, context,
feed, feed,
foldersData, foldersData!,
),
),
),
),
],
]
.intersperse(
const SizedBox(
height: 10,
), ),
)
.toList(),
), ),
), ),
), ),

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

@ -43,33 +43,14 @@ class NewsFoldersView extends StatelessWidget {
final feedsLoading, final feedsLoading,
final _, final _,
) => ) =>
RefreshIndicator( SortBoxBuilder<FoldersSortProperty, FolderFeedsWrapper>(
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, sortBox: foldersSortBox,
sortPropertyOption: bloc.options.foldersSortPropertyOption, sortPropertyOption: bloc.options.foldersSortPropertyOption,
sortBoxOrderOption: bloc.options.foldersSortBoxOrderOption, sortBoxOrderOption: bloc.options.foldersSortBoxOrderOption,
input: foldersData input: feedsData == null
.map( ? null
: foldersData
?.map(
(final folder) => FolderFeedsWrapper( (final folder) => FolderFeedsWrapper(
folder, folder,
feedsData.where((final feed) => feed.folderId == folder.id).toList(), feedsData.where((final feed) => feed.folderId == folder.id).toList(),
@ -80,21 +61,22 @@ class NewsFoldersView extends StatelessWidget {
scrollKey: 'news-folders', scrollKey: 'news-folders',
withFloatingActionButton: true, withFloatingActionButton: true,
items: sorted, items: sorted,
isLoading: feedsLoading || foldersLoading,
error: feedsError ?? foldersError,
onRetry: () {
bloc.refresh(
mainArticlesToo: false,
);
},
onRefresh: () async {
bloc.refresh(
mainArticlesToo: true,
);
},
builder: _buildFolder, builder: _buildFolder,
), ),
), ),
), ),
],
]
.intersperse(
const SizedBox(
height: 10,
),
)
.toList(),
),
),
),
), ),
); );

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

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

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

@ -19,29 +19,12 @@ class NotesCategoriesView extends StatelessWidget {
final notesLoading, final notesLoading,
final _, final _,
) => ) =>
RefreshIndicator( SortBoxBuilder<CategoriesSortProperty, NoteCategory>(
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, sortBox: categoriesSortBox,
sortPropertyOption: bloc.options.categoriesSortPropertyOption, sortPropertyOption: bloc.options.categoriesSortPropertyOption,
sortBoxOrderOption: bloc.options.categoriesSortBoxOrderOption, sortBoxOrderOption: bloc.options.categoriesSortBoxOrderOption,
input: notesData input: notesData
.map((final note) => note.category!) ?.map((final note) => note.category!)
.toSet() .toSet()
.map( .map(
(final category) => NoteCategory( (final category) => NoteCategory(
@ -53,20 +36,17 @@ class NotesCategoriesView extends StatelessWidget {
builder: (final context, final sorted) => CustomListView<NoteCategory>( builder: (final context, final sorted) => CustomListView<NoteCategory>(
scrollKey: 'notes-categories', scrollKey: 'notes-categories',
items: sorted, items: sorted,
isLoading: notesLoading,
error: notesError,
onRetry: () {
bloc.refresh();
},
onRefresh: () async {
bloc.refresh();
},
builder: _buildCategory, builder: _buildCategory,
), ),
), ),
),
],
]
.intersperse(
const SizedBox(
height: 10,
),
)
.toList(),
),
),
); );
Widget _buildCategory( Widget _buildCategory(

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

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

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

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

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

@ -42,41 +42,21 @@ class _NotificationsMainPageState extends State<NotificationsMainPage> {
}, },
child: const Icon(MdiIcons.checkAll), child: const Icon(MdiIcons.checkAll),
), ),
body: RefreshIndicator( body: CustomListView<NotificationsNotification>(
onRefresh: () async { scrollKey: 'notifications-notifications',
withFloatingActionButton: true,
items: notificationsData,
isLoading: notificationsLoading,
error: notificationsError,
onRetry: () {
widget.bloc.refresh(); widget.bloc.refresh();
}, },
child: Column( onRefresh: () async {
children: [
ExceptionWidget(
notificationsError,
onRetry: () {
widget.bloc.refresh(); widget.bloc.refresh();
}, },
),
CustomLinearProgressIndicator(
visible: notificationsLoading,
),
if (notificationsData != null) ...[
Expanded(
child: CustomListView<NotificationsNotification>(
scrollKey: 'notifications-notifications',
withFloatingActionButton: true,
items: notificationsData,
builder: _buildNotification, builder: _buildNotification,
), ),
), ),
],
]
.intersperse(
const SizedBox(
height: 10,
),
)
.toList(),
),
),
),
); );
Widget _buildNotification( Widget _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 SortBox<T, R> sortBox;
final SelectOption<T> sortPropertyOption; final SelectOption<T> sortPropertyOption;
final SelectOption<SortBoxOrder> sortBoxOrderOption; final SelectOption<SortBoxOrder> sortBoxOrderOption;
final List<R> input; final List<R>? input;
final Widget Function(BuildContext, List<R>) builder; final Widget Function(BuildContext, List<R>?) builder;
@override @override
Widget build(final BuildContext context) => OptionBuilder<T>( Widget build(final BuildContext context) => OptionBuilder<T>(
@ -25,7 +25,7 @@ class SortBoxBuilder<T extends Enum, R> extends StatelessWidget {
? Container() ? Container()
: builder( : builder(
context, context,
sortBox.sort(input, Box(property, order)), input == null ? null : sortBox.sort(input!, Box(property, order)),
), ),
), ),
); );

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

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

Loading…
Cancel
Save