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), child: const Icon(Icons.add),
) )
: null, : 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 { onRefresh: () async {
widget.bloc.refresh(); widget.bloc.refresh();
}, },
child: Column( builder: (final context, final widget) => widget,
children: [ topScrollingChildren: [
ExceptionWidget( Align(
filesError, alignment: Alignment.topLeft,
onRetry: () { child: Container(
widget.bloc.refresh(); margin: const EdgeInsets.symmetric(
}, horizontal: 10,
), ),
CustomLinearProgressIndicator( child: Wrap(
visible: filesLoading, crossAxisAlignment: WrapCrossAlignment.center,
), children: <Widget>[
Align( SizedBox(
alignment: Alignment.topLeft, height: 40,
child: Container( child: InkWell(
margin: const EdgeInsets.symmetric( onTap: () {
horizontal: 10, widget.bloc.setPath([]);
), },
child: Wrap( child: const Icon(Icons.house),
crossAxisAlignment: WrapCrossAlignment.center, ),
children: <Widget>[ ),
SizedBox( for (var i = 0; i < pathSnapshot.data!.length; i++) ...[
height: 40, InkWell(
child: InkWell( onTap: () {
onTap: () { widget.bloc.setPath(pathSnapshot.data!.sublist(0, i + 1));
widget.bloc.setPath([]); },
}, child: Text(pathSnapshot.data![i]),
child: const Icon(Icons.house),
),
), ),
for (var i = 0; i < pathSnapshot.data!.length; i++) ...[ ],
InkWell( ]
onTap: () { .intersperse(
widget.bloc.setPath(pathSnapshot.data!.sublist(0, i + 1)); const Icon(
}, Icons.keyboard_arrow_right,
child: Text(pathSnapshot.data![i]), size: 40,
), ),
], )
] .toList(),
.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
.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: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';

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

@ -45,26 +45,34 @@ 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,
onRetry: () { isLoading: articlesLoading || feedsLoading,
if (articlesError != null) { error: articlesError ?? feedsError,
widget.bloc.refresh(); onRetry: () {
} if (articlesError != null) {
if (feedsError != null) { widget.bloc.refresh();
widget.bloc.refreshNewsBloc(); }
} if (feedsError != null) {
}, widget.bloc.refreshNewsBloc();
), }
CustomLinearProgressIndicator( },
visible: articlesLoading || feedsLoading, 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>( 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(),
), ),
), ),
), ),

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

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

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

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

@ -19,52 +19,32 @@ class NotesCategoriesView extends StatelessWidget {
final notesLoading, final notesLoading,
final _, final _,
) => ) =>
RefreshIndicator( SortBoxBuilder<CategoriesSortProperty, NoteCategory>(
onRefresh: () async { sortBox: categoriesSortBox,
bloc.refresh(); sortPropertyOption: bloc.options.categoriesSortPropertyOption,
}, sortBoxOrderOption: bloc.options.categoriesSortBoxOrderOption,
child: Column( input: notesData
children: [ ?.map((final note) => note.category!)
ExceptionWidget( .toSet()
notesError, .map(
onRetry: () { (final category) => NoteCategory(
bloc.refresh(); category,
}, notesData.where((final note) => note.category == category).length,
),
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,
),
),
), ),
], )
] .toList(),
.intersperse( builder: (final context, final sorted) => CustomListView<NoteCategory>(
const SizedBox( scrollKey: 'notes-categories',
height: 10, items: sorted,
), isLoading: notesLoading,
) error: notesError,
.toList(), 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), child: const Icon(Icons.add),
), ),
body: RefreshIndicator( body: SortBoxBuilder<NotesSortProperty, NotesNote>(
onRefresh: () async { sortBox: notesSortBox,
bloc.refresh(); sortPropertyOption: bloc.options.notesSortPropertyOption,
}, sortBoxOrderOption: bloc.options.notesSortBoxOrderOption,
child: Column( input: category != null
children: [ ? notesData?.where((final note) => note.favorite! && note.category == category).toList()
ExceptionWidget( : notesData?.where((final note) => note.favorite!).toList(),
notesError, builder: (final context, final sortedFavorites) => SortBoxBuilder<NotesSortProperty, NotesNote>(
onRetry: () { sortBox: notesSortBox,
bloc.refresh(); sortPropertyOption: bloc.options.notesSortPropertyOption,
}, sortBoxOrderOption: bloc.options.notesSortBoxOrderOption,
), input: category != null
CustomLinearProgressIndicator( ? notesData?.where((final note) => !note.favorite! && note.category == category).toList()
visible: notesLoading, : notesData?.where((final note) => !note.favorite!).toList(),
), builder: (final context, final sortedNonFavorites) => CustomListView<NotesNote>(
if (notesData != null) ...[ scrollKey: 'notes-notes',
Expanded( withFloatingActionButton: true,
child: SortBoxBuilder<NotesSortProperty, NotesNote>( items: [
sortBox: notesSortBox, if (sortedFavorites != null) ...[
sortPropertyOption: bloc.options.notesSortPropertyOption, ...sortedFavorites,
sortBoxOrderOption: bloc.options.notesSortBoxOrderOption, ],
input: category != null if (sortedNonFavorites != null) ...[
? notesData.where((final note) => note.favorite! && note.category == category).toList() ...sortedNonFavorites,
: 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,
),
),
),
),
], ],
] isLoading: notesLoading,
.intersperse( error: notesError,
const SizedBox( onRetry: () {
height: 10, bloc.refresh();
), },
) onRefresh: () async {
.toList(), bloc.refresh();
},
builder: _buildNote,
),
), ),
), ),
), ),

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';

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

@ -42,39 +42,19 @@ class _NotificationsMainPageState extends State<NotificationsMainPage> {
}, },
child: const Icon(MdiIcons.checkAll), 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 { onRefresh: () async {
widget.bloc.refresh(); widget.bloc.refresh();
}, },
child: Column( builder: _buildNotification,
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(),
),
), ),
), ),
); );

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)),
), ),
), ),
); );

60
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,
key: scrollKey != null ? PageStorageKey<String>(scrollKey!) : null, child: Column(
padding: withFloatingActionButton ? const EdgeInsets.only(bottom: 88) : null, children: [
separatorBuilder: (final context, final index) => const SizedBox( if (topFixedChildren != null) ...[
height: 10, ...topFixedChildren!,
), ],
itemCount: items.length, CustomLinearProgressIndicator(
itemBuilder: (final context, final index) => builder(context, items[index]), 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