Browse Source

neon: rework ResultBuilder

pull/395/head
Nikolas Rimikis 1 year ago
parent
commit
6d8dd6b4b1
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 2
      packages/neon/neon/lib/src/app.dart
  2. 4
      packages/neon/neon/lib/src/pages/account_settings.dart
  3. 15
      packages/neon/neon/lib/src/pages/home.dart
  4. 8
      packages/neon/neon/lib/src/utils/request_manager.dart
  5. 40
      packages/neon/neon/lib/src/utils/result.dart
  6. 6
      packages/neon/neon/lib/src/widgets/account_tile.dart
  7. 4
      packages/neon/neon/lib/src/widgets/drawer.dart
  8. 57
      packages/neon/neon/lib/src/widgets/result_builder.dart
  9. 6
      packages/neon/neon/lib/src/widgets/user_avatar.dart
  10. 4
      packages/neon/neon_files/lib/widgets/browser_view.dart
  11. 4
      packages/neon/neon_news/lib/dialogs/add_feed.dart
  12. 6
      packages/neon/neon_news/lib/widgets/articles_view.dart
  13. 6
      packages/neon/neon_news/lib/widgets/feeds_view.dart
  14. 6
      packages/neon/neon_news/lib/widgets/folders_view.dart
  15. 4
      packages/neon/neon_notes/lib/dialogs/create_note.dart
  16. 4
      packages/neon/neon_notes/lib/dialogs/select_category.dart
  17. 4
      packages/neon/neon_notes/lib/widgets/categories_view.dart
  18. 4
      packages/neon/neon_notes/lib/widgets/notes_view.dart
  19. 4
      packages/neon/neon_notifications/lib/pages/main.dart

2
packages/neon/neon/lib/src/app.dart

@ -262,7 +262,7 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
}
FlutterNativeSplash.remove();
return ResultBuilder<Capabilities?>(
return ResultBuilder<Capabilities?>.behaviorSubject(
stream: activeAccountSnapshot.hasData
? _accountsBloc.getCapabilitiesBlocFor(activeAccountSnapshot.data!).capabilities
: null,

4
packages/neon/neon/lib/src/pages/account_settings.dart

@ -60,7 +60,7 @@ class AccountSettingsPage extends StatelessWidget {
),
],
),
body: ResultBuilder<NextcloudProvisioningApiUserDetails>(
body: ResultBuilder<NextcloudProvisioningApiUserDetails>.behaviorSubject(
stream: _userDetailsBloc.userDetails,
builder: (final context, final userDetails) => SettingsList(
categories: [
@ -92,7 +92,7 @@ class AccountSettingsPage extends StatelessWidget {
onRetry: _userDetailsBloc.refresh,
),
NeonLinearProgressIndicator(
visible: userDetails.loading,
visible: userDetails.isLoading,
),
],
),

15
packages/neon/neon/lib/src/pages/home.dart

@ -45,12 +45,12 @@ class _HomePageState extends State<HomePage> {
_capabilitiesBloc.capabilities.listen((final result) async {
if (result.data != null) {
// ignore cached version and prevent duplicate dialogs
if (result.cached) {
if (result.isCached) {
return;
}
_appsBloc.appImplementations.listen((final appsResult) async {
// ignore cached version and prevent duplicate dialogs
if (appsResult.data == null || appsResult.cached) {
if (appsResult.data == null || appsResult.isCached) {
return;
}
for (final id in [
@ -168,11 +168,12 @@ class _HomePageState extends State<HomePage> {
}
@override
Widget build(final BuildContext context) => ResultBuilder<Capabilities>(
Widget build(final BuildContext context) => ResultBuilder<Capabilities>.behaviorSubject(
stream: _capabilitiesBloc.capabilities,
builder: (final context, final capabilities) => ResultBuilder<Iterable<AppImplementation>>(
builder: (final context, final capabilities) => ResultBuilder<Iterable<AppImplementation>>.behaviorSubject(
stream: _appsBloc.appImplementations,
builder: (final context, final appImplementations) => ResultBuilder<NotificationsAppInterface?>(
builder: (final context, final appImplementations) =>
ResultBuilder<NotificationsAppInterface?>.behaviorSubject(
stream: _appsBloc.notificationsAppImplementation,
builder: (final context, final notificationsAppImplementation) => StreamBuilder<String?>(
stream: _appsBloc.activeAppID,
@ -204,7 +205,7 @@ class _HomePageState extends State<HomePage> {
),
),
],
if (appImplementations.error != null) ...[
if (appImplementations.hasError) ...[
const SizedBox(
width: 8,
),
@ -214,7 +215,7 @@ class _HomePageState extends State<HomePage> {
onlyIcon: true,
),
],
if (appImplementations.loading) ...[
if (appImplementations.isLoading) ...[
const SizedBox(
width: 8,
),

8
packages/neon/neon/lib/src/utils/request_manager.dart

@ -68,8 +68,8 @@ class RequestManager {
Result(
subject.value.data,
null,
loading: true,
cached: true,
isLoading: true,
isCached: true,
),
);
} else {
@ -148,8 +148,8 @@ class RequestManager {
Result(
cached,
error,
loading: loading,
cached: true,
isLoading: loading,
isCached: true,
),
);
return true;

40
packages/neon/neon/lib/src/utils/result.dart

@ -5,40 +5,56 @@ class Result<T> {
const Result(
this.data,
this.error, {
required this.loading,
required this.cached,
required this.isLoading,
required this.isCached,
});
factory Result.loading() => const Result(
null,
null,
loading: true,
cached: false,
isLoading: true,
isCached: false,
);
factory Result.success(final T data) => Result(
data,
null,
loading: false,
cached: false,
isLoading: false,
isCached: false,
);
factory Result.error(final Object error) => Result(
null,
error,
loading: false,
cached: false,
isLoading: false,
isCached: false,
);
final T? data;
final Object? error;
final bool loading;
final bool cached;
final bool isLoading;
final bool isCached;
Result<R> transform<R>(final R? Function(T data) call) => Result(
data != null ? call(data as T) : null,
error,
loading: loading,
cached: cached,
isLoading: isLoading,
isCached: isCached,
);
Result<T> asLoading() => Result(
data,
error,
isLoading: true,
isCached: isCached,
);
bool get hasError => error != null;
@override
bool operator ==(final Object other) =>
other is Result && other.isLoading == isLoading && other.data == data && other.error == error;
@override
int get hashCode => Object.hash(data, error, isLoading, isCached);
}

6
packages/neon/neon/lib/src/widgets/account_tile.dart

@ -36,7 +36,7 @@ class NeonAccountTile extends StatelessWidget {
leading: NeonUserAvatar(
account: account,
),
title: ResultBuilder<NextcloudProvisioningApiUserDetails>(
title: ResultBuilder<NextcloudProvisioningApiUserDetails>.behaviorSubject(
stream: userDetailsBloc.userDetails,
builder: (final context, final userDetails) => Row(
children: [
@ -51,7 +51,7 @@ class NeonAccountTile extends StatelessWidget {
),
),
],
if (userDetails.loading) ...[
if (userDetails.isLoading) ...[
const SizedBox(
width: 5,
),
@ -61,7 +61,7 @@ class NeonAccountTile extends StatelessWidget {
),
),
],
if (userDetails.error != null) ...[
if (userDetails.hasError) ...[
const SizedBox(
width: 5,
),

4
packages/neon/neon/lib/src/widgets/drawer.dart

@ -166,7 +166,7 @@ class NeonDrawerHeader extends StatelessWidget {
},
);
return ResultBuilder<Capabilities>(
return ResultBuilder<Capabilities>.behaviorSubject(
stream: capabilitiesBloc.capabilities,
builder: (final context, final capabilities) => DrawerHeader(
decoration: BoxDecoration(
@ -198,7 +198,7 @@ class NeonDrawerHeader extends StatelessWidget {
onRetry: capabilitiesBloc.refresh,
),
NeonLinearProgressIndicator(
visible: capabilities.loading,
visible: capabilities.isLoading,
),
],
accountSelecor,

57
packages/neon/neon/lib/src/widgets/result_builder.dart

@ -1,28 +1,51 @@
part of '../../neon.dart';
class ResultBuilder<R> extends StatelessWidget {
typedef ResultWidgetBuilder<T> = Widget Function(BuildContext context, Result<T> snapshot);
class ResultBuilder<T> extends StreamBuilderBase<Result<T>, Result<T>> {
const ResultBuilder({
required this.stream,
required this.builder,
this.initialData,
super.stream,
super.key,
});
final Stream<Result<R>?>? stream;
ResultBuilder.behaviorSubject({
required this.builder,
BehaviorSubject<Result<T>>? super.stream,
super.key,
}) : initialData = stream?.valueOrNull;
final ResultWidgetBuilder<T> builder;
final Result<T>? initialData;
@override
Result<T> initial() => initialData?.asLoading() ?? Result<T>.loading();
@override
Result<T> afterData(final Result<T> current, final Result<T> data) {
// prevent rebuild when only the cache state cahnges
if (current == data) {
return current;
}
return data;
}
@override
Result<T> afterError(final Result<T> current, final Object error, final StackTrace stackTrace) {
if (current.hasError) {
return current;
}
final Widget Function(BuildContext, Result<R>) builder;
return Result(
current.data,
error,
isLoading: false,
isCached: false,
);
}
@override
Widget build(final BuildContext context) => StreamBuilder(
stream: stream,
builder: (final context, final snapshot) {
if (snapshot.hasError) {
return builder(context, Result.error(snapshot.error!));
}
if (snapshot.hasData) {
return builder(context, snapshot.data!);
}
return builder(context, Result.loading());
},
);
Widget build(final BuildContext context, final Result<T> currentSummary) => builder(context, currentSummary);
}

6
packages/neon/neon/lib/src/widgets/user_avatar.dart

@ -74,7 +74,7 @@ class _UserAvatarState extends State<NeonUserAvatar> {
),
if (widget.showStatus) ...[
ResultBuilder<NextcloudUserStatusPublicStatus?>(
stream: _userStatusBloc.statuses.map((final statuses) => statuses[widget.username]),
stream: _userStatusBloc.statuses.mapNotNull((final statuses) => statuses[widget.username]),
builder: _userStatusIconBuilder,
),
],
@ -89,12 +89,12 @@ class _UserAvatarState extends State<NeonUserAvatar> {
Widget? child;
Decoration? decoration;
if (result.loading) {
if (result.isLoading) {
child = CircularProgressIndicator(
strokeWidth: 1.5,
color: widget.foregroundColor ?? Theme.of(context).colorScheme.onPrimary,
);
} else if (result.error != null) {
} else if (result.hasError) {
child = Icon(
Icons.error_outline,
size: scaledSize,

4
packages/neon/neon_files/lib/widgets/browser_view.dart

@ -32,7 +32,7 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
}
@override
Widget build(final BuildContext context) => ResultBuilder<List<WebDavFile>>(
Widget build(final BuildContext context) => ResultBuilder<List<WebDavFile>>.behaviorSubject(
stream: widget.bloc.files,
builder: (final context, final files) => StreamBuilder<List<String>>(
stream: widget.bloc.path,
@ -136,7 +136,7 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
],
],
],
isLoading: files.loading,
isLoading: files.isLoading,
error: files.error,
onRefresh: widget.bloc.refresh,
builder: (final context, final widget) => widget,

4
packages/neon/neon_news/lib/dialogs/add_feed.dart

@ -43,7 +43,7 @@ class _NewsAddFeedDialogState extends State<NewsAddFeedDialog> {
}
@override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNewsFolder>>(
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNewsFolder>>.behaviorSubject(
stream: widget.bloc.folders,
builder: (final context, final folders) => NeonDialog(
title: Text(AppLocalizations.of(context).feedAdd),
@ -74,7 +74,7 @@ class _NewsAddFeedDialogState extends State<NewsAddFeedDialog> {
),
Center(
child: NeonLinearProgressIndicator(
visible: folders.loading,
visible: folders.isLoading,
),
),
if (folders.data != null) ...[

6
packages/neon/neon_news/lib/widgets/articles_view.dart

@ -25,9 +25,9 @@ class _NewsArticlesViewState extends State<NewsArticlesView> {
}
@override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNewsFeed>>(
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNewsFeed>>.behaviorSubject(
stream: widget.newsBloc.feeds,
builder: (final context, final feeds) => ResultBuilder<List<NextcloudNewsArticle>>(
builder: (final context, final feeds) => ResultBuilder<List<NextcloudNewsArticle>>.behaviorSubject(
stream: widget.bloc.articles,
builder: (final context, final articles) => SortBoxBuilder<ArticlesSortProperty, NextcloudNewsArticle>(
sortBox: articlesSortBox,
@ -37,7 +37,7 @@ class _NewsArticlesViewState extends State<NewsArticlesView> {
builder: (final context, final sorted) => NeonListView<NextcloudNewsArticle>(
scrollKey: 'news-articles',
items: feeds.data == null ? null : sorted,
isLoading: articles.loading || feeds.loading,
isLoading: articles.isLoading || feeds.isLoading,
error: articles.error ?? feeds.error,
onRefresh: () async {
await Future.wait([

6
packages/neon/neon_news/lib/widgets/feeds_view.dart

@ -11,9 +11,9 @@ class NewsFeedsView extends StatelessWidget {
final int? folderID;
@override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNewsFolder>>(
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNewsFolder>>.behaviorSubject(
stream: bloc.folders,
builder: (final context, final folders) => ResultBuilder<List<NextcloudNewsFeed>>(
builder: (final context, final folders) => ResultBuilder<List<NextcloudNewsFeed>>.behaviorSubject(
stream: bloc.feeds,
builder: (final context, final feeds) => SortBoxBuilder<FeedsSortProperty, NextcloudNewsFeed>(
sortBox: feedsSortBox,
@ -26,7 +26,7 @@ class NewsFeedsView extends StatelessWidget {
scrollKey: 'news-feeds',
withFloatingActionButton: true,
items: sorted,
isLoading: feeds.loading || folders.loading,
isLoading: feeds.isLoading || folders.isLoading,
error: feeds.error ?? folders.error,
onRefresh: bloc.refresh,
builder: (final context, final feed) => _buildFeed(

6
packages/neon/neon_news/lib/widgets/folders_view.dart

@ -9,9 +9,9 @@ class NewsFoldersView extends StatelessWidget {
final NewsBloc bloc;
@override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNewsFolder>>(
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNewsFolder>>.behaviorSubject(
stream: bloc.folders,
builder: (final context, final folders) => ResultBuilder<List<NextcloudNewsFeed>>(
builder: (final context, final folders) => ResultBuilder<List<NextcloudNewsFeed>>.behaviorSubject(
stream: bloc.feeds,
builder: (final context, final feeds) => SortBoxBuilder<FoldersSortProperty, FolderFeedsWrapper>(
sortBox: foldersSortBox,
@ -30,7 +30,7 @@ class NewsFoldersView extends StatelessWidget {
scrollKey: 'news-folders',
withFloatingActionButton: true,
items: sorted,
isLoading: feeds.loading || folders.loading,
isLoading: feeds.isLoading || folders.isLoading,
error: feeds.error ?? folders.error,
onRefresh: bloc.refresh,
builder: _buildFolder,

4
packages/neon/neon_notes/lib/dialogs/create_note.dart

@ -26,7 +26,7 @@ class _NotesCreateNoteDialogState extends State<NotesCreateNoteDialog> {
}
@override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>(
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>.behaviorSubject(
stream: widget.bloc.notes,
builder: (final context, final notes) => NeonDialog(
title: Text(AppLocalizations.of(context).noteCreate),
@ -56,7 +56,7 @@ class _NotesCreateNoteDialogState extends State<NotesCreateNoteDialog> {
),
Center(
child: NeonLinearProgressIndicator(
visible: notes.loading,
visible: notes.isLoading,
),
),
if (notes.data != null) ...[

4
packages/neon/neon_notes/lib/dialogs/select_category.dart

@ -26,7 +26,7 @@ class _NotesSelectCategoryDialogState extends State<NotesSelectCategoryDialog> {
}
@override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>(
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>.behaviorSubject(
stream: widget.bloc.notes,
builder: (final context, final notes) => NeonDialog(
title: Text(AppLocalizations.of(context).category),
@ -44,7 +44,7 @@ class _NotesSelectCategoryDialogState extends State<NotesSelectCategoryDialog> {
),
Center(
child: NeonLinearProgressIndicator(
visible: notes.loading,
visible: notes.isLoading,
),
),
if (notes.data != null) ...[

4
packages/neon/neon_notes/lib/widgets/categories_view.dart

@ -9,7 +9,7 @@ class NotesCategoriesView extends StatelessWidget {
final NotesBloc bloc;
@override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>(
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>.behaviorSubject(
stream: bloc.notes,
builder: (final context, final notes) => SortBoxBuilder<CategoriesSortProperty, NoteCategory>(
sortBox: categoriesSortBox,
@ -28,7 +28,7 @@ class NotesCategoriesView extends StatelessWidget {
builder: (final context, final sorted) => NeonListView<NoteCategory>(
scrollKey: 'notes-categories',
items: sorted,
isLoading: notes.loading,
isLoading: notes.isLoading,
error: notes.error,
onRefresh: bloc.refresh,
builder: _buildCategory,

4
packages/neon/neon_notes/lib/widgets/notes_view.dart

@ -11,7 +11,7 @@ class NotesView extends StatelessWidget {
final String? category;
@override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>(
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>.behaviorSubject(
stream: bloc.notes,
builder: (final context, final notes) => SortBoxBuilder<NotesSortProperty, NextcloudNotesNote>(
sortBox: notesSortBox,
@ -34,7 +34,7 @@ class NotesView extends StatelessWidget {
...?sortedFavorites,
...?sortedNonFavorites,
],
isLoading: notes.loading,
isLoading: notes.isLoading,
error: notes.error,
onRefresh: bloc.refresh,
builder: _buildNote,

4
packages/neon/neon_notifications/lib/pages/main.dart

@ -24,7 +24,7 @@ class _NotificationsMainPageState extends State<NotificationsMainPage> {
}
@override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotificationsNotification>>(
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotificationsNotification>>.behaviorSubject(
stream: bloc.notifications,
builder: (final context, final notifications) => Scaffold(
resizeToAvoidBottomInset: false,
@ -39,7 +39,7 @@ class _NotificationsMainPageState extends State<NotificationsMainPage> {
scrollKey: 'notifications-notifications',
withFloatingActionButton: true,
items: notifications.data,
isLoading: notifications.loading,
isLoading: notifications.isLoading,
error: notifications.error,
onRefresh: bloc.refresh,
builder: _buildNotification,

Loading…
Cancel
Save