diff --git a/packages/neon/neon_dashboard/lib/src/blocs/dashboard.dart b/packages/neon/neon_dashboard/lib/src/blocs/dashboard.dart index 14509581478..c75733f8b1d 100644 --- a/packages/neon/neon_dashboard/lib/src/blocs/dashboard.dart +++ b/packages/neon/neon_dashboard/lib/src/blocs/dashboard.dart @@ -8,21 +8,20 @@ import 'package:neon_framework/models.dart'; import 'package:nextcloud/dashboard.dart' as dashboard; import 'package:rxdart/rxdart.dart'; -/// Events for [DashboardBloc]. -abstract class DashboardBlocEvents {} +/// Bloc for fetching dashboard widgets and their items. +sealed class DashboardBloc implements InteractiveBloc { + /// Creates a new Dashboard Bloc instance. + factory DashboardBloc(final Account account) => _DashboardBloc(account); -/// States for [DashboardBloc]. -abstract class DashboardBlocStates { /// Dashboard widgets that are displayed. BehaviorSubject>> get widgets; } -/// Implements the business logic for fetching dashboard widgets and their items. -class DashboardBloc extends InteractiveBloc implements DashboardBlocEvents, DashboardBlocStates { - /// Creates a new Dashboard Bloc instance. - /// - /// Automatically starts fetching the widgets and their items and refreshes everything every 30 seconds. - DashboardBloc(this._account) { +/// Implementation of [DashboardBloc]. +/// +/// Automatically starts fetching the widgets and their items and refreshes everything every 30 seconds. +class _DashboardBloc extends InteractiveBloc implements DashboardBloc { + _DashboardBloc(this._account) { unawaited(refresh()); _timer = TimerBloc().registerTimer(const Duration(seconds: 30), refresh); diff --git a/packages/neon/neon_dashboard/test/widget_test.dart b/packages/neon/neon_dashboard/test/widget_test.dart index b905dea6d3e..8f556770240 100644 --- a/packages/neon/neon_dashboard/test/widget_test.dart +++ b/packages/neon/neon_dashboard/test/widget_test.dart @@ -15,6 +15,7 @@ import 'package:neon_framework/widgets.dart'; import 'package:nextcloud/dashboard.dart' as dashboard; import 'package:rxdart/rxdart.dart'; +// ignore: subtype_of_sealed_class class MockAccountsBloc extends Mock implements AccountsBloc {} class MockCacheManager extends Mock implements DefaultCacheManager {} diff --git a/packages/neon/neon_files/lib/src/blocs/browser.dart b/packages/neon/neon_files/lib/src/blocs/browser.dart index 0176fd0fa01..0adce1afe54 100644 --- a/packages/neon/neon_files/lib/src/blocs/browser.dart +++ b/packages/neon/neon_files/lib/src/blocs/browser.dart @@ -7,20 +7,31 @@ import 'package:neon_framework/utils.dart'; import 'package:nextcloud/webdav.dart'; import 'package:rxdart/rxdart.dart'; -abstract interface class FilesBrowserBlocEvents { +sealed class FilesBrowserBloc implements InteractiveBloc { + factory FilesBrowserBloc( + final FilesOptions options, + final Account account, { + final PathUri? initialPath, + }) => + _FilesBrowserBloc( + options, + account, + initialPath: initialPath, + ); + void setPath(final PathUri uri); void createFolder(final PathUri uri); -} -abstract interface class FilesBrowserBlocStates { BehaviorSubject>> get files; BehaviorSubject get uri; + + FilesOptions get options; } -class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents, FilesBrowserBlocStates { - FilesBrowserBloc( +class _FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBloc { + _FilesBrowserBloc( this.options, this.account, { final PathUri? initialPath, @@ -32,6 +43,7 @@ class FilesBrowserBloc extends InteractiveBloc implements FilesBrowserBlocEvents unawaited(refresh()); } + @override final FilesOptions options; final Account account; diff --git a/packages/neon/neon_files/lib/src/blocs/files.dart b/packages/neon/neon_files/lib/src/blocs/files.dart index ef0136e3a83..4887c76675d 100644 --- a/packages/neon/neon_files/lib/src/blocs/files.dart +++ b/packages/neon/neon_files/lib/src/blocs/files.dart @@ -18,7 +18,16 @@ import 'package:rxdart/rxdart.dart'; import 'package:share_plus/share_plus.dart'; import 'package:universal_io/io.dart'; -abstract interface class FilesBlocEvents { +sealed class FilesBloc implements InteractiveBloc { + factory FilesBloc( + final FilesOptions options, + final Account account, + ) => + _FilesBloc( + options, + account, + ); + void uploadFile(final PathUri uri, final String localPath); void syncFile(final PathUri uri); @@ -38,14 +47,18 @@ abstract interface class FilesBlocEvents { void addFavorite(final PathUri uri); void removeFavorite(final PathUri uri); -} -abstract interface class FilesBlocStates { BehaviorSubject> get tasks; + + FilesOptions get options; + + FilesBrowserBloc get browser; + + FilesBrowserBloc getNewFilesBrowserBloc({final PathUri? initialUri}); } -class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocStates { - FilesBloc( +class _FilesBloc extends InteractiveBloc implements FilesBloc { + _FilesBloc( this.options, this.account, ) { @@ -53,8 +66,10 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta options.downloadQueueParallelism.addListener(_downloadParallelismListener); } + @override final FilesOptions options; final Account account; + @override late final browser = getNewFilesBrowserBloc(); final _uploadQueue = Queue(); @@ -217,6 +232,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta tasks.add(tasks.value..remove(task)); } + @override FilesBrowserBloc getNewFilesBrowserBloc({ final PathUri? initialUri, }) => diff --git a/packages/neon/neon_news/lib/src/blocs/article.dart b/packages/neon/neon_news/lib/src/blocs/article.dart index 5508ba9daf8..2028656c77a 100644 --- a/packages/neon/neon_news/lib/src/blocs/article.dart +++ b/packages/neon/neon_news/lib/src/blocs/article.dart @@ -2,11 +2,23 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:neon_framework/blocs.dart'; +import 'package:neon_framework/models.dart'; import 'package:neon_news/src/blocs/articles.dart'; import 'package:nextcloud/news.dart' as news; import 'package:rxdart/rxdart.dart'; -abstract interface class NewsArticleBlocEvents { +sealed class NewsArticleBloc implements InteractiveBloc { + factory NewsArticleBloc( + final NewsArticlesBloc articlesBloc, + final Account account, + final news.Article article, + ) => + _NewsArticleBloc( + articlesBloc, + account, + article, + ); + void markArticleAsRead(); void markArticleAsUnread(); @@ -14,17 +26,16 @@ abstract interface class NewsArticleBlocEvents { void starArticle(); void unstarArticle(); -} -abstract interface class NewsArticleBlocStates { BehaviorSubject get unread; BehaviorSubject get starred; } -class NewsArticleBloc extends InteractiveBloc implements NewsArticleBlocEvents, NewsArticleBlocStates { - NewsArticleBloc( +class _NewsArticleBloc extends InteractiveBloc implements NewsArticleBloc { + _NewsArticleBloc( this._newsArticlesBloc, + this.account, final news.Article article, ) { _id = article.id; @@ -33,6 +44,7 @@ class NewsArticleBloc extends InteractiveBloc implements NewsArticleBlocEvents, } final NewsArticlesBloc _newsArticlesBloc; + final Account account; late final int _id; @@ -50,12 +62,12 @@ class NewsArticleBloc extends InteractiveBloc implements NewsArticleBlocEvents, BehaviorSubject unread = BehaviorSubject(); @override - void refresh() {} + Future refresh() async {} @override void markArticleAsRead() { _wrapArticleAction(() async { - await _newsArticlesBloc.account.client.news.markArticleAsRead(itemId: _id); + await account.client.news.markArticleAsRead(itemId: _id); unread.add(false); }); } @@ -63,7 +75,7 @@ class NewsArticleBloc extends InteractiveBloc implements NewsArticleBlocEvents, @override void markArticleAsUnread() { _wrapArticleAction(() async { - await _newsArticlesBloc.account.client.news.markArticleAsUnread(itemId: _id); + await account.client.news.markArticleAsUnread(itemId: _id); unread.add(true); }); } @@ -71,7 +83,7 @@ class NewsArticleBloc extends InteractiveBloc implements NewsArticleBlocEvents, @override void starArticle() { _wrapArticleAction(() async { - await _newsArticlesBloc.account.client.news.starArticle(itemId: _id); + await account.client.news.starArticle(itemId: _id); starred.add(true); }); } @@ -79,7 +91,7 @@ class NewsArticleBloc extends InteractiveBloc implements NewsArticleBlocEvents, @override void unstarArticle() { _wrapArticleAction(() async { - await _newsArticlesBloc.account.client.news.unstarArticle(itemId: _id); + await account.client.news.unstarArticle(itemId: _id); starred.add(false); }); } diff --git a/packages/neon/neon_news/lib/src/blocs/articles.dart b/packages/neon/neon_news/lib/src/blocs/articles.dart index be71bd29a0e..9dab3813419 100644 --- a/packages/neon/neon_news/lib/src/blocs/articles.dart +++ b/packages/neon/neon_news/lib/src/blocs/articles.dart @@ -19,7 +19,22 @@ enum ListType { folder, } -abstract interface class NewsArticlesBlocEvents { +sealed class NewsArticlesBloc implements InteractiveBloc { + factory NewsArticlesBloc( + final NewsBloc newsBloc, + final NewsOptions options, + final Account account, { + final int? id, + final ListType? listType, + }) => + _NewsArticlesBloc( + newsBloc, + options, + account, + id: id, + listType: listType, + ); + void setFilterType(final FilterType type); void markArticleAsRead(final news.Article article); @@ -29,15 +44,17 @@ abstract interface class NewsArticlesBlocEvents { void starArticle(final news.Article article); void unstarArticle(final news.Article article); -} -abstract interface class NewsArticlesBlocStates { BehaviorSubject>> get articles; BehaviorSubject get filterType; + + NewsOptions get options; + + ListType? get listType; } -class NewsMainArticlesBloc extends NewsArticlesBloc { +class NewsMainArticlesBloc extends _NewsArticlesBloc { NewsMainArticlesBloc( super._newsBloc, super.options, @@ -45,8 +62,8 @@ class NewsMainArticlesBloc extends NewsArticlesBloc { ); } -class NewsArticlesBloc extends InteractiveBloc implements NewsArticlesBlocEvents, NewsArticlesBlocStates { - NewsArticlesBloc( +class _NewsArticlesBloc extends InteractiveBloc implements NewsArticlesBloc { + _NewsArticlesBloc( this._newsBloc, this.options, this.account, { @@ -64,9 +81,11 @@ class NewsArticlesBloc extends InteractiveBloc implements NewsArticlesBlocEvents } final NewsBloc _newsBloc; + @override final NewsOptions options; final Account account; final int? id; + @override final ListType? listType; @override diff --git a/packages/neon/neon_news/lib/src/blocs/news.dart b/packages/neon/neon_news/lib/src/blocs/news.dart index df245afd3b9..8bdfc95946c 100644 --- a/packages/neon/neon_news/lib/src/blocs/news.dart +++ b/packages/neon/neon_news/lib/src/blocs/news.dart @@ -8,7 +8,16 @@ import 'package:neon_news/src/options.dart'; import 'package:nextcloud/news.dart' as news; import 'package:rxdart/rxdart.dart'; -abstract interface class NewsBlocEvents { +sealed class NewsBloc implements InteractiveBloc { + factory NewsBloc( + final NewsOptions options, + final Account account, + ) => + _NewsBloc( + options, + account, + ); + void addFeed(final String url, final int? folderId); void removeFeed(final int feedId); @@ -26,18 +35,20 @@ abstract interface class NewsBlocEvents { void renameFolder(final int folderId, final String name); void markFolderAsRead(final int folderId); -} -abstract interface class NewsBlocStates { BehaviorSubject>> get folders; BehaviorSubject>> get feeds; BehaviorSubject get unreadCounter; + + NewsOptions get options; + + NewsMainArticlesBloc get mainArticlesBloc; } -class NewsBloc extends InteractiveBloc implements NewsBlocEvents, NewsBlocStates, NewsMainArticlesBloc { - NewsBloc( +class _NewsBloc extends InteractiveBloc implements NewsBloc, NewsMainArticlesBloc { + _NewsBloc( this.options, this.account, ) { @@ -55,8 +66,8 @@ class NewsBloc extends InteractiveBloc implements NewsBlocEvents, NewsBlocStates @override final NewsOptions options; @override - @override final Account account; + @override late final mainArticlesBloc = NewsMainArticlesBloc( this, options, diff --git a/packages/neon/neon_news/lib/src/pages/feed.dart b/packages/neon/neon_news/lib/src/pages/feed.dart index 42beda10d81..fa4a21b2731 100644 --- a/packages/neon/neon_news/lib/src/pages/feed.dart +++ b/packages/neon/neon_news/lib/src/pages/feed.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:neon_framework/blocs.dart'; +import 'package:neon_framework/utils.dart'; import 'package:neon_news/src/blocs/articles.dart'; import 'package:neon_news/src/blocs/news.dart'; import 'package:neon_news/src/widgets/articles_view.dart'; @@ -25,7 +27,7 @@ class NewsFeedPage extends StatelessWidget { bloc: NewsArticlesBloc( bloc, bloc.options, - bloc.account, + NeonProvider.of(context).activeAccount.value!, id: feed.id, listType: ListType.feed, ), diff --git a/packages/neon/neon_news/lib/src/widgets/articles_view.dart b/packages/neon/neon_news/lib/src/widgets/articles_view.dart index 56b7b38e840..aec4c2c044a 100644 --- a/packages/neon/neon_news/lib/src/widgets/articles_view.dart +++ b/packages/neon/neon_news/lib/src/widgets/articles_view.dart @@ -5,6 +5,7 @@ import 'package:neon_framework/blocs.dart'; import 'package:neon_framework/platform.dart'; import 'package:neon_framework/sort_box.dart'; import 'package:neon_framework/theme.dart'; +import 'package:neon_framework/utils.dart'; import 'package:neon_framework/widgets.dart'; import 'package:neon_news/l10n/localizations.dart'; import 'package:neon_news/src/blocs/article.dart'; @@ -207,12 +208,15 @@ class _NewsArticlesViewState extends State { debugPrint(s.toString()); } + final account = NeonProvider.of(context).activeAccount.value!; + if ((viewType == ArticleViewType.direct || article.url == null) && bodyData != null) { await Navigator.of(context).push( MaterialPageRoute( builder: (final context) => NewsArticlePage( bloc: NewsArticleBloc( widget.bloc, + account, article, ), articlesBloc: widget.bloc, @@ -230,6 +234,7 @@ class _NewsArticlesViewState extends State { builder: (final context) => NewsArticlePage( bloc: NewsArticleBloc( widget.bloc, + account, article, ), articlesBloc: widget.bloc, diff --git a/packages/neon/neon_news/lib/src/widgets/folder_view.dart b/packages/neon/neon_news/lib/src/widgets/folder_view.dart index a423095e558..939ba61659d 100644 --- a/packages/neon/neon_news/lib/src/widgets/folder_view.dart +++ b/packages/neon/neon_news/lib/src/widgets/folder_view.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:neon_framework/blocs.dart'; +import 'package:neon_framework/utils.dart'; import 'package:neon_news/src/blocs/articles.dart'; import 'package:neon_news/src/blocs/news.dart'; import 'package:neon_news/src/options.dart'; @@ -53,7 +55,7 @@ class _NewsFolderViewState extends State { bloc: NewsArticlesBloc( widget.bloc, widget.bloc.options, - widget.bloc.account, + NeonProvider.of(context).activeAccount.value!, id: widget.folder.id, listType: ListType.folder, ), diff --git a/packages/neon/neon_notes/lib/src/blocs/note.dart b/packages/neon/neon_notes/lib/src/blocs/note.dart index f5683bba467..05111e1aed7 100644 --- a/packages/neon/neon_notes/lib/src/blocs/note.dart +++ b/packages/neon/neon_notes/lib/src/blocs/note.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:neon_framework/blocs.dart'; +import 'package:neon_framework/models.dart'; import 'package:neon_notes/src/blocs/notes.dart'; import 'package:neon_notes/src/options.dart'; import 'package:nextcloud/nextcloud.dart'; @@ -9,21 +10,37 @@ import 'package:nextcloud/notes.dart' as notes; import 'package:queue/queue.dart'; import 'package:rxdart/rxdart.dart'; -abstract interface class NotesNoteBlocEvents { +sealed class NotesNoteBloc implements InteractiveBloc { + factory NotesNoteBloc( + final NotesBloc notesBloc, + final Account account, + final notes.Note note, + ) => + _NotesNoteBloc( + notesBloc, + account, + note, + ); + void updateContent(final String content); void updateTitle(final String title); void updateCategory(final String category); -} -abstract interface class NotesNoteBlocStates { BehaviorSubject get category; + + NotesOptions get options; + + String get initialContent; + + String get initialTitle; } -class NotesNoteBloc extends InteractiveBloc implements NotesNoteBlocEvents, NotesNoteBlocStates { - NotesNoteBloc( +class _NotesNoteBloc extends InteractiveBloc implements NotesNoteBloc { + _NotesNoteBloc( this._notesBloc, + this.account, final notes.Note note, ) { _emitNote(note); @@ -52,12 +69,17 @@ class NotesNoteBloc extends InteractiveBloc implements NotesNoteBlocEvents, Note }); } - late final NotesOptions options = _notesBloc.options; + @override + NotesOptions get options => _notesBloc.options; + final NotesBloc _notesBloc; + final Account account; final _updateQueue = Queue(); late final int id; + @override late final String initialContent; + @override late final String initialTitle; late String _etag; @@ -71,12 +93,12 @@ class NotesNoteBloc extends InteractiveBloc implements NotesNoteBlocEvents, Note BehaviorSubject category = BehaviorSubject(); @override - void refresh() {} + Future refresh() async {} @override void updateCategory(final String category) { _wrapAction( - (final etag) async => _notesBloc.account.client.notes.updateNote( + (final etag) async => account.client.notes.updateNote( id: id, category: category, ifMatch: '"$etag"', @@ -87,7 +109,7 @@ class NotesNoteBloc extends InteractiveBloc implements NotesNoteBlocEvents, Note @override void updateContent(final String content) { _wrapAction( - (final etag) async => _notesBloc.account.client.notes.updateNote( + (final etag) async => account.client.notes.updateNote( id: id, content: content, ifMatch: '"$etag"', @@ -98,7 +120,7 @@ class NotesNoteBloc extends InteractiveBloc implements NotesNoteBlocEvents, Note @override void updateTitle(final String title) { _wrapAction( - (final etag) async => _notesBloc.account.client.notes.updateNote( + (final etag) async => account.client.notes.updateNote( id: id, title: title, ifMatch: '"$etag"', diff --git a/packages/neon/neon_notes/lib/src/blocs/notes.dart b/packages/neon/neon_notes/lib/src/blocs/notes.dart index a31dbc42bf6..f36a3ce7117 100644 --- a/packages/neon/neon_notes/lib/src/blocs/notes.dart +++ b/packages/neon/neon_notes/lib/src/blocs/notes.dart @@ -8,7 +8,16 @@ import 'package:neon_notes/src/options.dart'; import 'package:nextcloud/notes.dart' as notes; import 'package:rxdart/rxdart.dart'; -abstract interface class NotesBlocEvents { +sealed class NotesBloc implements InteractiveBloc { + factory NotesBloc( + final NotesOptions options, + final Account account, + ) => + _NotesBloc( + options, + account, + ); + void createNote({ final String title = '', final String category = '', @@ -24,20 +33,21 @@ abstract interface class NotesBlocEvents { }); void deleteNote(final int id); -} -abstract interface class NotesBlocStates { BehaviorSubject>> get notesList; + + NotesOptions get options; } -class NotesBloc extends InteractiveBloc implements NotesBlocEvents, NotesBlocStates { - NotesBloc( +class _NotesBloc extends InteractiveBloc implements NotesBloc { + _NotesBloc( this.options, this.account, ) { unawaited(refresh()); } + @override final NotesOptions options; final Account account; diff --git a/packages/neon/neon_notes/lib/src/widgets/notes_view.dart b/packages/neon/neon_notes/lib/src/widgets/notes_view.dart index 9845112a13c..72f1683dc3b 100644 --- a/packages/neon/neon_notes/lib/src/widgets/notes_view.dart +++ b/packages/neon/neon_notes/lib/src/widgets/notes_view.dart @@ -95,6 +95,7 @@ class NotesView extends StatelessWidget { builder: (final context) => NotesNotePage( bloc: NotesNoteBloc( bloc, + NeonProvider.of(context).activeAccount.value!, note, ), notesBloc: bloc, diff --git a/packages/neon/neon_notifications/lib/src/blocs/notifications.dart b/packages/neon/neon_notifications/lib/src/blocs/notifications.dart index d5dba15f4ad..07afe77ba85 100644 --- a/packages/neon/neon_notifications/lib/src/blocs/notifications.dart +++ b/packages/neon/neon_notifications/lib/src/blocs/notifications.dart @@ -7,21 +7,25 @@ import 'package:neon_notifications/src/options.dart'; import 'package:nextcloud/notifications.dart' as notifications; import 'package:rxdart/rxdart.dart'; -abstract interface class NotificationsBlocEvents { +sealed class NotificationsBloc implements NotificationsBlocInterface, InteractiveBloc { + factory NotificationsBloc( + final NotificationsOptions options, + final Account account, + ) => + _NotificationsBloc(options, account); + + @override void deleteNotification(final int id); void deleteAllNotifications(); -} -abstract interface class NotificationsBlocStates { BehaviorSubject>> get notificationsList; BehaviorSubject get unreadCounter; } -class NotificationsBloc extends InteractiveBloc - implements NotificationsBlocInterface, NotificationsBlocEvents, NotificationsBlocStates { - NotificationsBloc( +class _NotificationsBloc extends InteractiveBloc implements NotificationsBlocInterface, NotificationsBloc { + _NotificationsBloc( this.options, this._account, ) { @@ -35,7 +39,6 @@ class NotificationsBloc extends InteractiveBloc _timer = TimerBloc().registerTimer(const Duration(seconds: 30), refresh); } - @override final NotificationsOptions options; final Account _account; late final NeonTimer _timer; diff --git a/packages/neon/neon_notifications/lib/src/options.dart b/packages/neon/neon_notifications/lib/src/options.dart index 37efd6dbf19..118cec6d496 100644 --- a/packages/neon/neon_notifications/lib/src/options.dart +++ b/packages/neon/neon_notifications/lib/src/options.dart @@ -1,7 +1,6 @@ -import 'package:neon_framework/models.dart'; import 'package:neon_framework/settings.dart'; -class NotificationsOptions extends AppImplementationOptions implements NotificationsOptionsInterface { +class NotificationsOptions extends AppImplementationOptions { NotificationsOptions(super.storage) { super.categories = []; super.options = []; diff --git a/packages/neon/neon_notifications/lib/src/pages/main.dart b/packages/neon/neon_notifications/lib/src/pages/main.dart index 6e438152bb3..8ead9636338 100644 --- a/packages/neon/neon_notifications/lib/src/pages/main.dart +++ b/packages/neon/neon_notifications/lib/src/pages/main.dart @@ -104,7 +104,7 @@ class _NotificationsMainPageState extends State { if (app != null) { // TODO: use go_router once implemented final accountsBloc = NeonProvider.of(context); - await accountsBloc.activeAppsBloc.setActiveApp(app.id); + accountsBloc.activeAppsBloc.setActiveApp(app.id); } else { final colorScheme = Theme.of(context).colorScheme; diff --git a/packages/neon_framework/lib/blocs.dart b/packages/neon_framework/lib/blocs.dart index 0ac66b7a87d..cbb26f2c708 100644 --- a/packages/neon_framework/lib/blocs.dart +++ b/packages/neon_framework/lib/blocs.dart @@ -2,4 +2,4 @@ export 'package:neon_framework/src/bloc/bloc.dart'; export 'package:neon_framework/src/bloc/result.dart'; // TODO: Remove access to the AccountsBloc. Apps should not need to access this export 'package:neon_framework/src/blocs/accounts.dart' show AccountsBloc; -export 'package:neon_framework/src/blocs/timer.dart' hide TimerBlocEvents, TimerBlocStates; +export 'package:neon_framework/src/blocs/timer.dart'; diff --git a/packages/neon_framework/lib/src/app.dart b/packages/neon_framework/lib/src/app.dart index ad4390683d5..4762c212d08 100644 --- a/packages/neon_framework/lib/src/app.dart +++ b/packages/neon_framework/lib/src/app.dart @@ -245,7 +245,7 @@ class _NeonAppState extends State with WidgetsBindingObserver, tray.Tra } Future _openAppFromExternal(final Account account, final String id) async { - await _accountsBloc.getAppsBlocFor(account).setActiveApp(id); + _accountsBloc.getAppsBlocFor(account).setActiveApp(id); _navigatorKey.currentState!.popUntil((final route) => route.settings.name == 'home'); await _showAndRestoreWindow(); } diff --git a/packages/neon_framework/lib/src/bloc/bloc.dart b/packages/neon_framework/lib/src/bloc/bloc.dart index e9224bcb50a..dade5fcac4d 100644 --- a/packages/neon_framework/lib/src/bloc/bloc.dart +++ b/packages/neon_framework/lib/src/bloc/bloc.dart @@ -35,7 +35,7 @@ abstract class InteractiveBloc extends Bloc { /// Refreshes the state of the bloc. /// /// Commonly involves re-fetching data from the server. - FutureOr refresh(); + Future refresh(); /// Adds an error to the [errors] state. @protected diff --git a/packages/neon_framework/lib/src/blocs/accounts.dart b/packages/neon_framework/lib/src/blocs/accounts.dart index bc8bec0e431..eb0e448af84 100644 --- a/packages/neon_framework/lib/src/blocs/accounts.dart +++ b/packages/neon_framework/lib/src/blocs/accounts.dart @@ -13,6 +13,7 @@ import 'package:neon_framework/src/blocs/user_statuses.dart'; import 'package:neon_framework/src/models/account.dart'; import 'package:neon_framework/src/models/account_cache.dart'; import 'package:neon_framework/src/models/app_implementation.dart'; +import 'package:neon_framework/src/models/disposable.dart'; import 'package:neon_framework/src/settings/models/storage.dart'; import 'package:neon_framework/src/utils/account_options.dart'; import 'package:neon_framework/src/utils/findable.dart'; @@ -22,34 +23,40 @@ import 'package:rxdart/rxdart.dart'; const _keyAccounts = 'accounts'; -/// Events for the [AccountsBloc]. -@internal -abstract interface class AccountsBlocEvents { +/// The Bloc responsible for managing the [Account]s +// TODO: Make this class properly sealed once we do not need mocks for the Bloc anymore. +@sealed +abstract interface class AccountsBloc implements Disposable { + factory AccountsBloc( + final GlobalOptions globalOptions, + final Iterable allAppImplementations, + ) => + _AccountsBloc( + globalOptions, + allAppImplementations, + ); + /// Logs in the given [account]. /// - /// It will also call [setActiveAccount] when no other accounts are registered in [AccountsBlocStates.accounts]. + /// It will also call [setActiveAccount] when no other accounts are registered in [AccountsBloc.accounts]. void addAccount(final Account account); /// Logs out the given [account]. /// - /// If [account] is the current [AccountsBlocStates.activeAccount] it will automatically activate the first one in [AccountsBlocStates.accounts]. - /// It is not defined whether listeners of [AccountsBlocStates.accounts] or [AccountsBlocStates.activeAccount] are informed first. + /// If [account] is the current [AccountsBloc.activeAccount] it will automatically activate the first one in [AccountsBloc.accounts]. + /// It is not defined whether listeners of [AccountsBloc.accounts] or [AccountsBloc.activeAccount] are informed first. void removeAccount(final Account account); /// Updates the given [account]. /// - /// It triggers an event in both [AccountsBlocStates.accounts] and [AccountsBlocStates.activeAccount] to inform all listeners. + /// It triggers an event in both [AccountsBloc.accounts] and [AccountsBloc.activeAccount] to inform all listeners. void updateAccount(final Account account); /// Sets the active [account]. /// - /// It triggers an event in [AccountsBlocStates.activeAccount] to inform all listeners. + /// It triggers an event in [AccountsBloc.activeAccount] to inform all listeners. void setActiveAccount(final Account account); -} -/// States for the [AccountsBloc]. -@internal -abstract interface class AccountsBlocStates { /// All registered accounts. /// /// An empty value represents a state where no account is logged in. @@ -60,15 +67,78 @@ abstract interface class AccountsBlocStates { /// It should be ensured to only emit an event when it's value changes. /// A null value represents a state where no user is logged in. BehaviorSubject get activeAccount; + + /// Whether accounts are logged in. + bool get hasAccounts; + + /// The options for the [activeAccount]. + /// + /// Convenience method for [getOptionsFor] with the currently active account. + AccountOptions get activeOptions; + + /// The options for the specified [account]. + /// + /// Use [activeOptions] to get them for the [activeAccount]. + AccountOptions getOptionsFor(final Account account); + + /// The appsBloc for the [activeAccount]. + /// + /// Convenience method for [getAppsBlocFor] with the currently active account. + AppsBloc get activeAppsBloc; + + /// The appsBloc for the specified [account]. + /// + /// Use [activeAppsBloc] to get them for the [activeAccount]. + AppsBloc getAppsBlocFor(final Account account); + + /// The capabilitiesBloc for the [activeAccount]. + /// + /// Convenience method for [getCapabilitiesBlocFor] with the currently active account. + CapabilitiesBloc get activeCapabilitiesBloc; + + /// The capabilitiesBloc for the specified [account]. + /// + /// Use [activeCapabilitiesBloc] to get them for the [activeAccount]. + CapabilitiesBloc getCapabilitiesBlocFor(final Account account); + + /// The userDetailsBloc for the [activeAccount]. + /// + /// Convenience method for [getUserDetailsBlocFor] with the currently active account. + UserDetailsBloc get activeUserDetailsBloc; + + /// The userDetailsBloc for the specified [account]. + /// + /// Use [activeUserDetailsBloc] to get them for the [activeAccount]. + UserDetailsBloc getUserDetailsBlocFor(final Account account); + + /// The userStatusBloc for the [activeAccount]. + /// + /// Convenience method for [getUserStatusesBlocFor] with the currently active account. + UserStatusesBloc get activeUserStatusesBloc; + + /// The userStatusBloc for the specified [account]. + /// + /// Use [activeUserStatusesBloc] to get them for the [activeAccount]. + UserStatusesBloc getUserStatusesBlocFor(final Account account); + + /// The UnifiedSearchBloc for the [activeAccount]. + /// + /// Convenience method for [getUnifiedSearchBlocFor] with the currently active account. + UnifiedSearchBloc get activeUnifiedSearchBloc; + + /// The UnifiedSearchBloc for the specified [account]. + /// + /// Use [activeUnifiedSearchBloc] to get them for the [activeAccount]. + UnifiedSearchBloc getUnifiedSearchBlocFor(final Account account); } -/// The Bloc responsible for managing the [Account]s -class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocStates { +/// Implementation of [AccountsBloc]. +class _AccountsBloc extends Bloc implements AccountsBloc { /// Creates a new account bloc. /// /// The last state will be loaded from storage and all necessary listeners /// will be set up. - AccountsBloc( + _AccountsBloc( this._globalOptions, this._allAppImplementations, ) { @@ -222,30 +292,22 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState return aa; } - /// Whether accounts are logged in. + @override bool get hasAccounts => activeAccount.valueOrNull != null; - /// The options for the [activeAccount]. - /// - /// Convenience method for [getOptionsFor] with the currently active account. + @override AccountOptions get activeOptions => getOptionsFor(aa); - /// The options for the specified [account]. - /// - /// Use [activeOptions] to get them for the [activeAccount]. + @override AccountOptions getOptionsFor(final Account account) => _accountsOptions[account] ??= AccountOptions( AppStorage(StorageKeys.accounts, account.id), getAppsBlocFor(account), ); - /// The appsBloc for the [activeAccount]. - /// - /// Convenience method for [getAppsBlocFor] with the currently active account. + @override AppsBloc get activeAppsBloc => getAppsBlocFor(aa); - /// The appsBloc for the specified [account]. - /// - /// Use [activeAppsBloc] to get them for the [activeAccount]. + @override AppsBloc getAppsBlocFor(final Account account) => _appsBlocs[account] ??= AppsBloc( getCapabilitiesBlocFor(account), this, @@ -253,47 +315,31 @@ class AccountsBloc extends Bloc implements AccountsBlocEvents, AccountsBlocState _allAppImplementations, ); - /// The capabilitiesBloc for the [activeAccount]. - /// - /// Convenience method for [getCapabilitiesBlocFor] with the currently active account. + @override CapabilitiesBloc get activeCapabilitiesBloc => getCapabilitiesBlocFor(aa); - /// The capabilitiesBloc for the specified [account]. - /// - /// Use [activeCapabilitiesBloc] to get them for the [activeAccount]. + @override CapabilitiesBloc getCapabilitiesBlocFor(final Account account) => _capabilitiesBlocs[account] ??= CapabilitiesBloc(account); - /// The userDetailsBloc for the [activeAccount]. - /// - /// Convenience method for [getUserDetailsBlocFor] with the currently active account. - UserDetailsBloc get activeUerDetailsBloc => getUserDetailsBlocFor(aa); + @override + UserDetailsBloc get activeUserDetailsBloc => getUserDetailsBlocFor(aa); - /// The userDetailsBloc for the specified [account]. - /// - /// Use [activeUerDetailsBloc] to get them for the [activeAccount]. + @override UserDetailsBloc getUserDetailsBlocFor(final Account account) => _userDetailsBlocs[account] ??= UserDetailsBloc(account); - /// The userStatusBloc for the [activeAccount]. - /// - /// Convenience method for [getUserStatusesBlocFor] with the currently active account. + @override UserStatusesBloc get activeUserStatusesBloc => getUserStatusesBlocFor(aa); - /// The userStatusBloc for the specified [account]. - /// - /// Use [activeUserStatusesBloc] to get them for the [activeAccount]. + @override UserStatusesBloc getUserStatusesBlocFor(final Account account) => _userStatusesBlocs[account] ??= UserStatusesBloc(account); - /// The UnifiedSearchBloc for the [activeAccount]. - /// - /// Convenience method for [getUnifiedSearchBlocFor] with the currently active account. + @override UnifiedSearchBloc get activeUnifiedSearchBloc => getUnifiedSearchBlocFor(aa); - /// The UnifiedSearchBloc for the specified [account]. - /// - /// Use [activeUnifiedSearchBloc] to get them for the [activeAccount]. + @override UnifiedSearchBloc getUnifiedSearchBlocFor(final Account account) => _unifiedSearchBlocs[account] ??= UnifiedSearchBloc( getAppsBlocFor(account), diff --git a/packages/neon_framework/lib/src/blocs/apps.dart b/packages/neon_framework/lib/src/blocs/apps.dart index 84ba73c88db..2cc68232b3b 100644 --- a/packages/neon_framework/lib/src/blocs/apps.dart +++ b/packages/neon_framework/lib/src/blocs/apps.dart @@ -17,19 +17,28 @@ import 'package:nextcloud/nextcloud.dart'; import 'package:provider/provider.dart'; import 'package:rxdart/rxdart.dart'; -/// Events for the [AppsBloc]. +/// The Bloc responsible for managing the [AppImplementation]s. @internal -abstract interface class AppsBlocEvents { +sealed class AppsBloc implements InteractiveBloc { + factory AppsBloc( + final CapabilitiesBloc capabilitiesBloc, + final AccountsBloc accountsBloc, + final Account account, + final Iterable allAppImplementations, + ) => + _AppsBloc( + capabilitiesBloc, + accountsBloc, + account, + allAppImplementations, + ); + /// Sets the active app using the [appID]. /// /// If the app is already the active app nothing will happen. /// When using [skipAlreadySet] nothing will be done if there already is an active app. void setActiveApp(final String appID, {final bool skipAlreadySet = false}); -} -/// States for the [AppsBloc]. -@internal -abstract interface class AppsBlocStates { /// A collection of clients used in the app drawer. /// /// It does not contain clients for that are specially handled like for the notifications. @@ -46,13 +55,20 @@ abstract interface class AppsBlocStates { /// A collection of unsupported apps and their minimum required version. BehaviorSubject> get appVersionChecks; + + /// Returns the active [Bloc] for the given [appImplementation]. + /// + /// If no bloc exists yet a new one will be instantiated and cached in [AppImplementation.blocsCache]. + T getAppBloc(final AppImplementation appImplementation); + + /// Returns the active [Bloc] for every registered [AppImplementation] wrapped in a Provider. + List> get appBlocProviders; } -/// The Bloc responsible for managing the [AppImplementation]s. -@internal -class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates { +/// Implementation of [AppsBloc]. +class _AppsBloc extends InteractiveBloc implements AppsBloc { /// Creates a new apps bloc. - AppsBloc( + _AppsBloc( this._capabilitiesBloc, this._accountsBloc, this._account, @@ -241,13 +257,11 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates } } - /// Returns the active [Bloc] for the given [appImplementation]. - /// - /// If no bloc exists yet a new one will be instantiated and cached in [AppImplementation.blocsCache]. + @override T getAppBloc(final AppImplementation appImplementation) => appImplementation.getBloc(_account); - /// Returns the active [Bloc] for every registered [AppImplementation] wrapped in a Provider. + @override List> get appBlocProviders => _allAppImplementations.map((final appImplementation) => appImplementation.blocProvider).toList(); } diff --git a/packages/neon_framework/lib/src/blocs/capabilities.dart b/packages/neon_framework/lib/src/blocs/capabilities.dart index 3ccdb0f7e87..f6d2d09ecc2 100644 --- a/packages/neon_framework/lib/src/blocs/capabilities.dart +++ b/packages/neon_framework/lib/src/blocs/capabilities.dart @@ -9,17 +9,15 @@ import 'package:nextcloud/core.dart' as core; import 'package:rxdart/rxdart.dart'; @internal -abstract interface class CapabilitiesBlocEvents {} +sealed class CapabilitiesBloc implements InteractiveBloc { + /// Creates a new capabilities bloc. + factory CapabilitiesBloc(final Account account) => _CapabilitiesBloc(account); -@internal -abstract interface class CapabilitiesBlocStates { BehaviorSubject> get capabilities; } -@internal -class CapabilitiesBloc extends InteractiveBloc implements CapabilitiesBlocEvents, CapabilitiesBlocStates { - /// Creates a new capabilities bloc. - CapabilitiesBloc( +class _CapabilitiesBloc extends InteractiveBloc implements CapabilitiesBloc { + _CapabilitiesBloc( this._account, ) { unawaited(refresh()); diff --git a/packages/neon_framework/lib/src/blocs/first_launch.dart b/packages/neon_framework/lib/src/blocs/first_launch.dart index 5c751aee935..7f33273a055 100644 --- a/packages/neon_framework/lib/src/blocs/first_launch.dart +++ b/packages/neon_framework/lib/src/blocs/first_launch.dart @@ -2,21 +2,23 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:neon_framework/src/bloc/bloc.dart'; +import 'package:neon_framework/src/models/disposable.dart'; import 'package:neon_framework/src/settings/models/storage.dart'; import 'package:rxdart/rxdart.dart'; @internal -abstract interface class FirstLaunchBlocEvents {} +sealed class FirstLaunchBloc implements Disposable { + factory FirstLaunchBloc({ + final bool disabled = false, + }) => + _FirstLaunchBloc(disabled: disabled); -@internal -abstract interface class FirstLaunchBlocStates { BehaviorSubject get onFirstLaunch; } @immutable -@internal -class FirstLaunchBloc extends Bloc implements FirstLaunchBlocEvents, FirstLaunchBlocStates { - FirstLaunchBloc({ +class _FirstLaunchBloc extends Bloc implements FirstLaunchBloc { + _FirstLaunchBloc({ final bool disabled = false, }) { const storage = SingleValueStorage(StorageKeys.firstLaunch); diff --git a/packages/neon_framework/lib/src/blocs/login_check_account.dart b/packages/neon_framework/lib/src/blocs/login_check_account.dart index 2892fb7e403..ccee7869e35 100644 --- a/packages/neon_framework/lib/src/blocs/login_check_account.dart +++ b/packages/neon_framework/lib/src/blocs/login_check_account.dart @@ -11,18 +11,24 @@ import 'package:nextcloud/provisioning_api.dart' as provisioning_api; import 'package:rxdart/rxdart.dart'; @internal -abstract interface class LoginCheckAccountBlocEvents {} +sealed class LoginCheckAccountBloc implements InteractiveBloc { + factory LoginCheckAccountBloc( + final Uri serverURL, + final String loginName, + final String password, + ) => + _LoginCheckAccountBloc( + serverURL, + loginName, + password, + ); -@internal -abstract interface class LoginCheckAccountBlocStates { /// Contains the account for the user BehaviorSubject> get state; } -@internal -class LoginCheckAccountBloc extends InteractiveBloc - implements LoginCheckAccountBlocEvents, LoginCheckAccountBlocStates { - LoginCheckAccountBloc( +class _LoginCheckAccountBloc extends InteractiveBloc implements LoginCheckAccountBloc { + _LoginCheckAccountBloc( this.serverURL, this.loginName, this.password, diff --git a/packages/neon_framework/lib/src/blocs/login_check_server_status.dart b/packages/neon_framework/lib/src/blocs/login_check_server_status.dart index d08c209d48c..4fea2c5d779 100644 --- a/packages/neon_framework/lib/src/blocs/login_check_server_status.dart +++ b/packages/neon_framework/lib/src/blocs/login_check_server_status.dart @@ -10,18 +10,15 @@ import 'package:nextcloud/nextcloud.dart'; import 'package:rxdart/rxdart.dart'; @internal -abstract interface class LoginCheckServerStatusBlocEvents {} +sealed class LoginCheckServerStatusBloc implements InteractiveBloc { + factory LoginCheckServerStatusBloc(final Uri serverURL) => _LoginCheckServerStatusBloc(serverURL); -@internal -abstract interface class LoginCheckServerStatusBlocStates { /// Contains the current server connection state BehaviorSubject> get state; } -@internal -class LoginCheckServerStatusBloc extends InteractiveBloc - implements LoginCheckServerStatusBlocEvents, LoginCheckServerStatusBlocStates { - LoginCheckServerStatusBloc(this.serverURL) { +class _LoginCheckServerStatusBloc extends InteractiveBloc implements LoginCheckServerStatusBloc { + _LoginCheckServerStatusBloc(this.serverURL) { unawaited(refresh()); } diff --git a/packages/neon_framework/lib/src/blocs/login_flow.dart b/packages/neon_framework/lib/src/blocs/login_flow.dart index 1d1570d682e..4004ad0b276 100644 --- a/packages/neon_framework/lib/src/blocs/login_flow.dart +++ b/packages/neon_framework/lib/src/blocs/login_flow.dart @@ -10,18 +10,16 @@ import 'package:nextcloud/nextcloud.dart'; import 'package:rxdart/rxdart.dart'; @internal -abstract interface class LoginFlowBlocEvents {} +sealed class LoginFlowBloc implements InteractiveBloc { + factory LoginFlowBloc(final Uri serverURL) => _LoginFlowBloc(serverURL); -@internal -abstract interface class LoginFlowBlocStates { BehaviorSubject> get init; Stream get result; } -@internal -class LoginFlowBloc extends InteractiveBloc implements LoginFlowBlocEvents, LoginFlowBlocStates { - LoginFlowBloc(this.serverURL) { +class _LoginFlowBloc extends InteractiveBloc implements LoginFlowBloc { + _LoginFlowBloc(this.serverURL) { unawaited(refresh()); } diff --git a/packages/neon_framework/lib/src/blocs/next_push.dart b/packages/neon_framework/lib/src/blocs/next_push.dart index 4833f753126..25bfeb460f0 100644 --- a/packages/neon_framework/lib/src/blocs/next_push.dart +++ b/packages/neon_framework/lib/src/blocs/next_push.dart @@ -5,21 +5,29 @@ import 'package:meta/meta.dart'; import 'package:neon_framework/src/bloc/bloc.dart'; import 'package:neon_framework/src/blocs/accounts.dart'; import 'package:neon_framework/src/models/account.dart'; +import 'package:neon_framework/src/models/disposable.dart'; import 'package:neon_framework/src/utils/global_options.dart'; import 'package:nextcloud/uppush.dart' as uppush; import 'package:rxdart/rxdart.dart'; @internal -abstract interface class NextPushBlocEvents {} +sealed class NextPushBloc implements Disposable { + factory NextPushBloc( + final AccountsBloc accountsBloc, + final GlobalOptions globalOptions, { + final bool disabled = false, + }) => + _NextPushBloc( + accountsBloc, + globalOptions, + disabled: disabled, + ); -@internal -abstract interface class NextPushBlocStates { BehaviorSubject get onNextPushSupported; } -@internal -class NextPushBloc extends Bloc implements NextPushBlocEvents, NextPushBlocStates { - NextPushBloc( +class _NextPushBloc extends Bloc implements NextPushBloc { + _NextPushBloc( this._accountsBloc, this._globalOptions, { final bool disabled = false, diff --git a/packages/neon_framework/lib/src/blocs/push_notifications.dart b/packages/neon_framework/lib/src/blocs/push_notifications.dart index b8cbec0f2f9..1731a70ce07 100644 --- a/packages/neon_framework/lib/src/blocs/push_notifications.dart +++ b/packages/neon_framework/lib/src/blocs/push_notifications.dart @@ -15,14 +15,19 @@ import 'package:nextcloud/notifications.dart' as notifications; import 'package:unifiedpush/unifiedpush.dart'; @internal -abstract interface class PushNotificationsBlocEvents {} - -@internal -abstract interface class PushNotificationsBlocStates {} +sealed class PushNotificationsBloc { + factory PushNotificationsBloc( + final AccountsBloc accountsBloc, + final GlobalOptions globalOptions, + ) => + _PushNotificationsBloc( + accountsBloc, + globalOptions, + ); +} -@internal -class PushNotificationsBloc extends Bloc implements PushNotificationsBlocEvents, PushNotificationsBlocStates { - PushNotificationsBloc( +class _PushNotificationsBloc extends Bloc implements PushNotificationsBloc { + _PushNotificationsBloc( this._accountsBloc, this._globalOptions, ) { diff --git a/packages/neon_framework/lib/src/blocs/timer.dart b/packages/neon_framework/lib/src/blocs/timer.dart index 11f81735ad1..78529234910 100644 --- a/packages/neon_framework/lib/src/blocs/timer.dart +++ b/packages/neon_framework/lib/src/blocs/timer.dart @@ -6,8 +6,21 @@ import 'dart:ui'; import 'package:meta/meta.dart'; import 'package:neon_framework/src/bloc/bloc.dart'; -@internal -abstract interface class TimerBlocEvents { +sealed class TimerBloc extends Bloc { + factory TimerBloc() => instance ??= _TimerBloc._(); + + @visibleForTesting + factory TimerBloc.mocked(final TimerBloc mock) => instance ??= mock; + + @visibleForTesting + static TimerBloc? instance; + + @override + void dispose() { + TimerBloc.instance?.dispose(); + TimerBloc.instance = null; + } + /// Register a [callback] that will be called periodically. /// The time between the executions is defined by the [duration]. NeonTimer registerTimer(final Duration duration, final VoidCallback callback); @@ -15,65 +28,55 @@ abstract interface class TimerBlocEvents { /// Unregister a timer that has been previously registered with the bloc. /// You can also use [NeonTimer.cancel]. void unregisterTimer(final NeonTimer timer); -} -@internal -abstract interface class TimerBlocStates {} + @visibleForTesting + Map get timers; + + @visibleForTesting + Map> get callbacks; +} /// Execute callbacks at defined periodic intervals. /// Components can register their callbacks and everything with the same periodicity will be executed at the same time. /// /// The [TimerBloc] is a singleton. /// Sub-second timers are not supported. -class TimerBloc extends Bloc implements TimerBlocEvents, TimerBlocStates { - factory TimerBloc() => instance ??= TimerBloc._(); - - @visibleForTesting - factory TimerBloc.mocked(final TimerBloc mock) => instance ??= mock; - - TimerBloc._(); - - @visibleForTesting - static TimerBloc? instance; +class _TimerBloc implements TimerBloc { + _TimerBloc._(); - final Map _timers = {}; - final Map> _callbacks = {}; - - @visibleForTesting - Map get timers => _timers; - - @visibleForTesting - Map> get callbacks => _callbacks; + @override + final Map timers = {}; + @override + final Map> callbacks = {}; @override void dispose() { - for (final timer in _timers.values) { + for (final timer in timers.values) { timer.cancel(); } - _timers.clear(); - _callbacks.clear(); - TimerBloc.instance = null; + timers.clear(); + callbacks.clear(); } @override NeonTimer registerTimer(final Duration duration, final VoidCallback callback) { - if (_timers[duration.inSeconds] == null) { - _timers[duration.inSeconds] = Timer.periodic(duration, (final _) { - for (final callback in _callbacks[duration.inSeconds]!) { + if (timers[duration.inSeconds] == null) { + timers[duration.inSeconds] = Timer.periodic(duration, (final _) { + for (final callback in callbacks[duration.inSeconds]!) { callback(); } }); - _callbacks[duration.inSeconds] = {callback}; + callbacks[duration.inSeconds] = {callback}; } else { - _callbacks[duration.inSeconds]!.add(callback); + callbacks[duration.inSeconds]!.add(callback); } return NeonTimer(duration, callback); } @override void unregisterTimer(final NeonTimer timer) { - if (_timers[timer.duration.inSeconds] != null) { - _callbacks[timer.duration.inSeconds]!.remove(timer.callback); + if (timers[timer.duration.inSeconds] != null) { + callbacks[timer.duration.inSeconds]!.remove(timer.callback); } } } diff --git a/packages/neon_framework/lib/src/blocs/unified_search.dart b/packages/neon_framework/lib/src/blocs/unified_search.dart index 5754f3a61e9..b2aa6f43979 100644 --- a/packages/neon_framework/lib/src/blocs/unified_search.dart +++ b/packages/neon_framework/lib/src/blocs/unified_search.dart @@ -11,24 +11,29 @@ import 'package:nextcloud/core.dart' as core; import 'package:rxdart/rxdart.dart'; @internal -abstract interface class UnifiedSearchBlocEvents { +sealed class UnifiedSearchBloc implements InteractiveBloc { + factory UnifiedSearchBloc( + final AppsBloc appsBloc, + final Account account, + ) => + _UnifiedSearchBloc( + appsBloc, + account, + ); + void search(final String term); void enable(); void disable(); -} -@internal -abstract interface class UnifiedSearchBlocStates { BehaviorSubject get enabled; BehaviorSubject>?>> get results; } -@internal -class UnifiedSearchBloc extends InteractiveBloc implements UnifiedSearchBlocEvents, UnifiedSearchBlocStates { - UnifiedSearchBloc( +class _UnifiedSearchBloc extends InteractiveBloc implements UnifiedSearchBloc { + _UnifiedSearchBloc( this._appsBloc, this._account, ) { diff --git a/packages/neon_framework/lib/src/blocs/user_details.dart b/packages/neon_framework/lib/src/blocs/user_details.dart index 163d89496de..bb19c9cf936 100644 --- a/packages/neon_framework/lib/src/blocs/user_details.dart +++ b/packages/neon_framework/lib/src/blocs/user_details.dart @@ -9,16 +9,14 @@ import 'package:nextcloud/provisioning_api.dart' as provisioning_api; import 'package:rxdart/rxdart.dart'; @internal -abstract interface class UserDetailsBlocEvents {} +sealed class UserDetailsBloc implements InteractiveBloc { + factory UserDetailsBloc(final Account account) => _UserDetailsBloc(account); -@internal -abstract interface class UserDetailsBlocStates { BehaviorSubject> get userDetails; } -@internal -class UserDetailsBloc extends InteractiveBloc implements UserDetailsBlocEvents, UserDetailsBlocStates { - UserDetailsBloc( +class _UserDetailsBloc extends InteractiveBloc implements UserDetailsBloc { + _UserDetailsBloc( this._account, ) { unawaited(refresh()); diff --git a/packages/neon_framework/lib/src/blocs/user_statuses.dart b/packages/neon_framework/lib/src/blocs/user_statuses.dart index 1bb112b6afd..8658e94e7d8 100644 --- a/packages/neon_framework/lib/src/blocs/user_statuses.dart +++ b/packages/neon_framework/lib/src/blocs/user_statuses.dart @@ -7,24 +7,23 @@ import 'package:neon_framework/src/bloc/bloc.dart'; import 'package:neon_framework/src/bloc/result.dart'; import 'package:neon_framework/src/blocs/timer.dart'; import 'package:neon_framework/src/models/account.dart'; +import 'package:neon_framework/src/models/disposable.dart'; import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/user_status.dart' as user_status; import 'package:rxdart/rxdart.dart'; import 'package:window_manager/window_manager.dart'; @internal -abstract interface class UserStatusesBlocEvents { +sealed class UserStatusesBloc implements Disposable { + factory UserStatusesBloc(final Account account) => _UserStatusesBloc(account); + void load(final String username, {final bool force = false}); -} -@internal -abstract interface class UserStatusesBlocStates { BehaviorSubject>> get statuses; } -@internal -class UserStatusesBloc extends InteractiveBloc implements UserStatusesBlocEvents, UserStatusesBlocStates { - UserStatusesBloc( +class _UserStatusesBloc extends InteractiveBloc implements UserStatusesBloc { + _UserStatusesBloc( this._account, ) { unawaited(refresh()); diff --git a/packages/neon_framework/lib/src/models/notifications_interface.dart b/packages/neon_framework/lib/src/models/notifications_interface.dart index 4d5b9194bff..7198a4642d3 100644 --- a/packages/neon_framework/lib/src/models/notifications_interface.dart +++ b/packages/neon_framework/lib/src/models/notifications_interface.dart @@ -7,7 +7,7 @@ import 'package:neon_framework/src/settings/models/options_collection.dart'; /// /// Use this to access the notifications client from other Neon clients. abstract interface class NotificationsAppInterface extends AppImplementation { + R extends AppImplementationOptions> extends AppImplementation { /// Creates a new notifications client. NotificationsAppInterface(); @@ -18,18 +18,6 @@ abstract interface class NotificationsAppInterface { _activeApp = index; }); - unawaited(_appsBloc.setActiveApp(_apps[index].id)); + _appsBloc.setActiveApp(_apps[index].id); //context.goNamed(apps[index].routeName); } diff --git a/packages/neon_framework/lib/src/widgets/user_avatar.dart b/packages/neon_framework/lib/src/widgets/user_avatar.dart index a4410c1938f..746fecb2d94 100644 --- a/packages/neon_framework/lib/src/widgets/user_avatar.dart +++ b/packages/neon_framework/lib/src/widgets/user_avatar.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:neon_framework/src/bloc/result.dart'; import 'package:neon_framework/src/blocs/accounts.dart'; @@ -57,7 +55,7 @@ class _UserAvatarState extends State { super.initState(); if (widget.showStatus) { - unawaited(_userStatusBloc.load(widget.username)); + _userStatusBloc.load(widget.username); } }