Browse Source

Merge pull request #395 from Leptopoda/feature/resultsubject

Feature/resultsubject
pull/398/head
Nikolas Rimikis 1 year ago committed by GitHub
parent
commit
2a52c49a8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 4
      packages/neon/neon/lib/l10n/en.arb
  3. 4
      packages/neon/neon/lib/l10n/localizations.dart
  4. 4
      packages/neon/neon/lib/l10n/localizations_en.dart
  5. 2
      packages/neon/neon/lib/src/app.dart
  6. 49
      packages/neon/neon/lib/src/blocs/apps.dart
  7. 4
      packages/neon/neon/lib/src/pages/account_settings.dart
  8. 62
      packages/neon/neon/lib/src/pages/home.dart
  9. 8
      packages/neon/neon/lib/src/utils/request_manager.dart
  10. 56
      packages/neon/neon/lib/src/utils/result.dart
  11. 6
      packages/neon/neon/lib/src/widgets/account_tile.dart
  12. 4
      packages/neon/neon/lib/src/widgets/drawer.dart
  13. 49
      packages/neon/neon/lib/src/widgets/result_builder.dart
  14. 6
      packages/neon/neon/lib/src/widgets/user_avatar.dart
  15. 84
      packages/neon/neon/test/result_test.dart
  16. 4
      packages/neon/neon_files/lib/widgets/browser_view.dart
  17. 4
      packages/neon/neon_news/lib/dialogs/add_feed.dart
  18. 6
      packages/neon/neon_news/lib/widgets/articles_view.dart
  19. 6
      packages/neon/neon_news/lib/widgets/feeds_view.dart
  20. 6
      packages/neon/neon_news/lib/widgets/folders_view.dart
  21. 4
      packages/neon/neon_notes/lib/dialogs/create_note.dart
  22. 4
      packages/neon/neon_notes/lib/dialogs/select_category.dart
  23. 4
      packages/neon/neon_notes/lib/widgets/categories_view.dart
  24. 4
      packages/neon/neon_notes/lib/widgets/notes_view.dart
  25. 4
      packages/neon/neon_notifications/lib/pages/main.dart

1
.gitignore vendored

@ -1,5 +1,6 @@
/.dart_tool/ /.dart_tool/
/pubspec.lock /pubspec.lock
packages/**/coverage
# Melos reccomends not adding them to vcs but we need them as we don't use melos in CI # Melos reccomends not adding them to vcs but we need them as we don't use melos in CI
# **/pubspec_overrides.yaml # **/pubspec_overrides.yaml

4
packages/neon/neon/lib/l10n/en.arb

@ -36,10 +36,10 @@
} }
}, },
"errorUnableToOpenFile": "Unable to open the file", "errorUnableToOpenFile": "Unable to open the file",
"errorUnsupportedVersion": "Sorry, this Nextcloud {name} version is not supported.", "errorUnsupportedVersion": "Sorry, the version of the following apps on your Nextcloud instance are not supported. \n {unsupported} \n Please contact your administrator to resolve the issues.",
"@errorUnsupportedVersion" : { "@errorUnsupportedVersion" : {
"placeholders": { "placeholders": {
"name": { "unsupported": {
"type": "String" "type": "String"
} }
} }

4
packages/neon/neon/lib/l10n/localizations.dart

@ -194,8 +194,8 @@ abstract class AppLocalizations {
/// No description provided for @errorUnsupportedVersion. /// No description provided for @errorUnsupportedVersion.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Sorry, this Nextcloud {name} version is not supported.'** /// **'Sorry, the version of the following apps on your Nextcloud instance are not supported. \n {unsupported} \n Please contact your administrator to resolve the issues.'**
String errorUnsupportedVersion(String name); String errorUnsupportedVersion(String unsupported);
/// No description provided for @errorEmptyField. /// No description provided for @errorEmptyField.
/// ///

4
packages/neon/neon/lib/l10n/localizations_en.dart

@ -79,8 +79,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get errorUnableToOpenFile => 'Unable to open the file'; String get errorUnableToOpenFile => 'Unable to open the file';
@override @override
String errorUnsupportedVersion(String name) { String errorUnsupportedVersion(String unsupported) {
return 'Sorry, this Nextcloud $name version is not supported.'; return 'Sorry, the version of the following apps on your Nextcloud instance are not supported. \n $unsupported \n Please contact your administrator to resolve the issues.';
} }
@override @override

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

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

49
packages/neon/neon/lib/src/blocs/apps.dart

@ -16,6 +16,8 @@ abstract class AppsBlocStates {
BehaviorSubject<String?> get activeAppID; BehaviorSubject<String?> get activeAppID;
BehaviorSubject get openNotifications; BehaviorSubject get openNotifications;
BehaviorSubject<Iterable<(String, Object?)>?> get appVersions;
} }
class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates { class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates {
@ -50,6 +52,8 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
} }
}), }),
); );
unawaited(_checkCompatibility());
} }
}); });
@ -59,11 +63,52 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
(final data) => data.capabilities.notifications != null ? _findAppImplementation('notifications') : null, (final data) => data.capabilities.notifications != null ? _findAppImplementation('notifications') : null,
), ),
); );
unawaited(_checkCompatibility());
}); });
unawaited(refresh()); unawaited(refresh());
} }
Future<void> _checkCompatibility() async {
final apps = appImplementations.valueOrNull;
final capabilities = _capabilitiesBloc.capabilities.valueOrNull;
// ignore cached data
if (capabilities == null || apps == null || !capabilities.hasUncachedData || !apps.hasUncachedData) {
return;
}
final appIds = {
'core',
...apps.requireData.map((final a) => a.id),
};
final notSupported = <(String, Object?)>[];
for (final id in appIds) {
try {
final (supported, minVersion) = switch (id) {
'core' => await _account.client.core.isSupported(capabilities.requireData),
'news' => await _account.client.news.isSupported(),
'notes' => await _account.client.notes.isSupported(capabilities.requireData),
_ => (true, null),
};
if (!supported) {
notSupported.add((id, minVersion));
}
} catch (e, s) {
debugPrint(e.toString());
debugPrint(s.toString());
}
}
if (notSupported.isNotEmpty) {
appVersions.add(notSupported);
}
}
T? _findAppImplementation<T extends AppImplementation>(final String id) { T? _findAppImplementation<T extends AppImplementation>(final String id) {
final matches = _filteredAppImplementations([id]); final matches = _filteredAppImplementations([id]);
if (matches.isNotEmpty) { if (matches.isNotEmpty) {
@ -89,6 +134,7 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
unawaited(notificationsAppImplementation.close()); unawaited(notificationsAppImplementation.close());
unawaited(activeAppID.close()); unawaited(activeAppID.close());
unawaited(openNotifications.close()); unawaited(openNotifications.close());
unawaited(appVersions.close());
for (final app in _allAppImplementations) { for (final app in _allAppImplementations) {
for (final bloc in app.blocs.values) { for (final bloc in app.blocs.values) {
@ -114,6 +160,9 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
@override @override
BehaviorSubject openNotifications = BehaviorSubject(); BehaviorSubject openNotifications = BehaviorSubject();
@override
BehaviorSubject<List<(String, Object?)>?> appVersions = BehaviorSubject();
@override @override
Future refresh() async { Future refresh() async {
await _requestManager.wrapNextcloud<List<NextcloudApp>, NextcloudCoreNavigationApps>( await _requestManager.wrapNextcloud<List<NextcloudApp>, NextcloudCoreNavigationApps>(

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, stream: _userDetailsBloc.userDetails,
builder: (final context, final userDetails) => SettingsList( builder: (final context, final userDetails) => SettingsList(
categories: [ categories: [
@ -92,7 +92,7 @@ class AccountSettingsPage extends StatelessWidget {
onRetry: _userDetailsBloc.refresh, onRetry: _userDetailsBloc.refresh,
), ),
NeonLinearProgressIndicator( NeonLinearProgressIndicator(
visible: userDetails.loading, visible: userDetails.isLoading,
), ),
], ],
), ),

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

@ -42,45 +42,26 @@ class _HomePageState extends State<HomePage> {
} }
}); });
_capabilitiesBloc.capabilities.listen((final result) async { _appsBloc.appVersions.listen((final values) {
if (result.data != null) { if (values == null || !mounted) {
// ignore cached version and prevent duplicate dialogs
if (result.cached) {
return; return;
} }
_appsBloc.appImplementations.listen((final appsResult) async {
// ignore cached version and prevent duplicate dialogs final l10n = AppLocalizations.of(context);
if (appsResult.data == null || appsResult.cached) {
return; final buffer = StringBuffer()..writeln();
}
for (final id in [ for (final error in values) {
'core', final (appId, minVersion) = error;
...appsResult.data!.map((final a) => a.id), final appName = l10n.appImplementationName(appId);
]) {
try { if (appName.isNotEmpty && minVersion != null) {
final (supported, _) = switch (id) { buffer.writeln('- $appName $minVersion');
'core' => await _account.client.core.isSupported(result.data),
'news' => await _account.client.news.isSupported(),
'notes' => await _account.client.notes.isSupported(result.data),
_ => (true, null),
};
if (supported || !mounted) {
return;
}
var name = AppLocalizations.of(context).appImplementationName(id);
if (name == '') {
name = id;
}
await _showProblem(
AppLocalizations.of(context).errorUnsupportedVersion(name),
);
} catch (e, s) {
debugPrint(e.toString());
debugPrint(s.toString());
}
} }
});
} }
final message = l10n.errorUnsupportedVersion(buffer.toString());
unawaited(_showProblem(message));
}); });
GlobalPopups().register(context); GlobalPopups().register(context);
@ -168,11 +149,12 @@ class _HomePageState extends State<HomePage> {
} }
@override @override
Widget build(final BuildContext context) => ResultBuilder<Capabilities>( Widget build(final BuildContext context) => ResultBuilder<Capabilities>.behaviorSubject(
stream: _capabilitiesBloc.capabilities, stream: _capabilitiesBloc.capabilities,
builder: (final context, final capabilities) => ResultBuilder<Iterable<AppImplementation>>( builder: (final context, final capabilities) => ResultBuilder<Iterable<AppImplementation>>.behaviorSubject(
stream: _appsBloc.appImplementations, stream: _appsBloc.appImplementations,
builder: (final context, final appImplementations) => ResultBuilder<NotificationsAppInterface?>( builder: (final context, final appImplementations) =>
ResultBuilder<NotificationsAppInterface?>.behaviorSubject(
stream: _appsBloc.notificationsAppImplementation, stream: _appsBloc.notificationsAppImplementation,
builder: (final context, final notificationsAppImplementation) => StreamBuilder<String?>( builder: (final context, final notificationsAppImplementation) => StreamBuilder<String?>(
stream: _appsBloc.activeAppID, stream: _appsBloc.activeAppID,
@ -204,7 +186,7 @@ class _HomePageState extends State<HomePage> {
), ),
), ),
], ],
if (appImplementations.error != null) ...[ if (appImplementations.hasError) ...[
const SizedBox( const SizedBox(
width: 8, width: 8,
), ),
@ -214,7 +196,7 @@ class _HomePageState extends State<HomePage> {
onlyIcon: true, onlyIcon: true,
), ),
], ],
if (appImplementations.loading) ...[ if (appImplementations.isLoading) ...[
const SizedBox( const SizedBox(
width: 8, width: 8,
), ),

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

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

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

@ -1,43 +1,71 @@
part of '../../neon.dart'; part of '../../neon.dart';
@immutable
class Result<T> { class Result<T> {
Result( const Result(
this.data, this.data,
this.error, { this.error, {
required this.loading, required this.isLoading,
required this.cached, required this.isCached,
}); });
factory Result.loading() => Result( factory Result.loading() => const Result(
null, null,
null, null,
loading: true, isLoading: true,
cached: false, isCached: false,
); );
factory Result.success(final T data) => Result( factory Result.success(final T data) => Result(
data, data,
null, null,
loading: false, isLoading: false,
cached: false, isCached: false,
); );
factory Result.error(final Object error) => Result( factory Result.error(final Object error) => Result(
null, null,
error, error,
loading: false, isLoading: false,
cached: false, isCached: false,
); );
final T? data; final T? data;
final Object? error; final Object? error;
final bool loading; final bool isLoading;
final bool cached; final bool isCached;
Result<R> transform<R>(final R? Function(T data) call) => Result( Result<R> transform<R>(final R? Function(T data) call) => Result(
data != null ? call(data as T) : null, data != null ? call(data as T) : null,
error, error,
loading: loading, isLoading: isLoading,
cached: cached, isCached: isCached,
); );
Result<T> asLoading() => Result(
data,
error,
isLoading: true,
isCached: isCached,
);
bool get hasError => error != null;
bool get hasData => data != null;
bool get hasUncachedData => hasData && !isCached;
T get requireData {
if (hasData) {
return data!;
}
throw StateError('Result has no data');
}
@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( leading: NeonUserAvatar(
account: account, account: account,
), ),
title: ResultBuilder<NextcloudProvisioningApiUserDetails>( title: ResultBuilder<NextcloudProvisioningApiUserDetails>.behaviorSubject(
stream: userDetailsBloc.userDetails, stream: userDetailsBloc.userDetails,
builder: (final context, final userDetails) => Row( builder: (final context, final userDetails) => Row(
children: [ children: [
@ -51,7 +51,7 @@ class NeonAccountTile extends StatelessWidget {
), ),
), ),
], ],
if (userDetails.loading) ...[ if (userDetails.isLoading) ...[
const SizedBox( const SizedBox(
width: 5, width: 5,
), ),
@ -61,7 +61,7 @@ class NeonAccountTile extends StatelessWidget {
), ),
), ),
], ],
if (userDetails.error != null) ...[ if (userDetails.hasError) ...[
const SizedBox( const SizedBox(
width: 5, 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, stream: capabilitiesBloc.capabilities,
builder: (final context, final capabilities) => DrawerHeader( builder: (final context, final capabilities) => DrawerHeader(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -198,7 +198,7 @@ class NeonDrawerHeader extends StatelessWidget {
onRetry: capabilitiesBloc.refresh, onRetry: capabilitiesBloc.refresh,
), ),
NeonLinearProgressIndicator( NeonLinearProgressIndicator(
visible: capabilities.loading, visible: capabilities.isLoading,
), ),
], ],
accountSelecor, accountSelecor,

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

@ -1,28 +1,51 @@
part of '../../neon.dart'; 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({ const ResultBuilder({
required this.stream,
required this.builder, required this.builder,
this.initialData,
super.stream,
super.key, 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;
final Widget Function(BuildContext, Result<R>) builder; @override
Result<T> initial() => initialData?.asLoading() ?? Result<T>.loading();
@override @override
Widget build(final BuildContext context) => StreamBuilder( Result<T> afterData(final Result<T> current, final Result<T> data) {
stream: stream, // prevent rebuild when only the cache state cahnges
builder: (final context, final snapshot) { if (current == data) {
if (snapshot.hasError) { return current;
return builder(context, Result.error(snapshot.error!)); }
return data;
} }
if (snapshot.hasData) {
return builder(context, snapshot.data!); @override
Result<T> afterError(final Result<T> current, final Object error, final StackTrace stackTrace) {
if (current.hasError) {
return current;
} }
return builder(context, Result.loading()); return Result(
}, current.data,
error,
isLoading: false,
isCached: false,
); );
} }
@override
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) ...[ if (widget.showStatus) ...[
ResultBuilder<NextcloudUserStatusPublicStatus?>( ResultBuilder<NextcloudUserStatusPublicStatus?>(
stream: _userStatusBloc.statuses.map((final statuses) => statuses[widget.username]), stream: _userStatusBloc.statuses.mapNotNull((final statuses) => statuses[widget.username]),
builder: _userStatusIconBuilder, builder: _userStatusIconBuilder,
), ),
], ],
@ -89,12 +89,12 @@ class _UserAvatarState extends State<NeonUserAvatar> {
Widget? child; Widget? child;
Decoration? decoration; Decoration? decoration;
if (result.loading) { if (result.isLoading) {
child = CircularProgressIndicator( child = CircularProgressIndicator(
strokeWidth: 1.5, strokeWidth: 1.5,
color: widget.foregroundColor ?? Theme.of(context).colorScheme.onPrimary, color: widget.foregroundColor ?? Theme.of(context).colorScheme.onPrimary,
); );
} else if (result.error != null) { } else if (result.hasError) {
child = Icon( child = Icon(
Icons.error_outline, Icons.error_outline,
size: scaledSize, size: scaledSize,

84
packages/neon/neon/test/result_test.dart

@ -0,0 +1,84 @@
import 'package:neon/neon.dart';
import 'package:test/test.dart';
void main() {
group('Result', () {
test('Equality', () {
const data = 'someData';
const a = Result(
data,
null,
isLoading: true,
isCached: false,
);
const b = Result(
data,
null,
isLoading: true,
isCached: true,
);
expect(a, equals(a), reason: 'identical');
expect(a, equals(b), reason: 'ignore cached state in equality');
expect(a.hashCode, equals(a.hashCode), reason: 'identical');
expect(a.hashCode, isNot(equals(b.hashCode)), reason: 'hashcode should respect the cached state');
});
test('Transform to loading', () {
const data = 'someData';
final a = Result.success(data);
const b = Result(
data,
null,
isLoading: true,
isCached: false,
);
expect(a, isNot(equals(b)));
expect(a.asLoading(), equals(b));
});
test('data check', () {
const data = 'someData';
final a = Result.loading();
final b = Result.success(data);
const c = Result(
data,
null,
isLoading: false,
isCached: true,
);
expect(a.hasData, false);
expect(b.hasData, true);
expect(() => a.requireData, throwsStateError);
expect(b.requireData, equals(data));
expect(b.hasUncachedData, true);
expect(c.hasUncachedData, false);
});
test('error check', () {
const error = 'someError';
final a = Result.error(error);
expect(a.hasError, true);
});
test('transform', () {
const data = 1;
final a = Result.success(data);
String transformer(final int data) => data.toString();
expect(a.transform(transformer), equals(Result.success(data.toString())));
});
});
}

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

@ -32,7 +32,7 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
} }
@override @override
Widget build(final BuildContext context) => ResultBuilder<List<WebDavFile>>( Widget build(final BuildContext context) => ResultBuilder<List<WebDavFile>>.behaviorSubject(
stream: widget.bloc.files, stream: widget.bloc.files,
builder: (final context, final files) => StreamBuilder<List<String>>( builder: (final context, final files) => StreamBuilder<List<String>>(
stream: widget.bloc.path, stream: widget.bloc.path,
@ -136,7 +136,7 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
], ],
], ],
], ],
isLoading: files.loading, isLoading: files.isLoading,
error: files.error, error: files.error,
onRefresh: widget.bloc.refresh, onRefresh: widget.bloc.refresh,
builder: (final context, final widget) => widget, 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 @override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNewsFolder>>( Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNewsFolder>>.behaviorSubject(
stream: widget.bloc.folders, stream: widget.bloc.folders,
builder: (final context, final folders) => NeonDialog( builder: (final context, final folders) => NeonDialog(
title: Text(AppLocalizations.of(context).feedAdd), title: Text(AppLocalizations.of(context).feedAdd),
@ -74,7 +74,7 @@ class _NewsAddFeedDialogState extends State<NewsAddFeedDialog> {
), ),
Center( Center(
child: NeonLinearProgressIndicator( child: NeonLinearProgressIndicator(
visible: folders.loading, visible: folders.isLoading,
), ),
), ),
if (folders.data != null) ...[ if (folders.data != null) ...[

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

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

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

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

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

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

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

@ -26,7 +26,7 @@ class _NotesSelectCategoryDialogState extends State<NotesSelectCategoryDialog> {
} }
@override @override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>( Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>.behaviorSubject(
stream: widget.bloc.notes, stream: widget.bloc.notes,
builder: (final context, final notes) => NeonDialog( builder: (final context, final notes) => NeonDialog(
title: Text(AppLocalizations.of(context).category), title: Text(AppLocalizations.of(context).category),
@ -44,7 +44,7 @@ class _NotesSelectCategoryDialogState extends State<NotesSelectCategoryDialog> {
), ),
Center( Center(
child: NeonLinearProgressIndicator( child: NeonLinearProgressIndicator(
visible: notes.loading, visible: notes.isLoading,
), ),
), ),
if (notes.data != null) ...[ 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; final NotesBloc bloc;
@override @override
Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>( Widget build(final BuildContext context) => ResultBuilder<List<NextcloudNotesNote>>.behaviorSubject(
stream: bloc.notes, stream: bloc.notes,
builder: (final context, final notes) => SortBoxBuilder<CategoriesSortProperty, NoteCategory>( builder: (final context, final notes) => SortBoxBuilder<CategoriesSortProperty, NoteCategory>(
sortBox: categoriesSortBox, sortBox: categoriesSortBox,
@ -28,7 +28,7 @@ class NotesCategoriesView extends StatelessWidget {
builder: (final context, final sorted) => NeonListView<NoteCategory>( builder: (final context, final sorted) => NeonListView<NoteCategory>(
scrollKey: 'notes-categories', scrollKey: 'notes-categories',
items: sorted, items: sorted,
isLoading: notes.loading, isLoading: notes.isLoading,
error: notes.error, error: notes.error,
onRefresh: bloc.refresh, onRefresh: bloc.refresh,
builder: _buildCategory, builder: _buildCategory,

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

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

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

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

Loading…
Cancel
Save