A framework for building convergent cross-platform Nextcloud clients using Flutter.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

258 lines
7.0 KiB

import 'dart:async';
import 'package:neon/src/apps/news/app.dart';
import 'package:neon/src/apps/news/blocs/articles.dart';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/neon.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:rx_bloc/rx_bloc.dart';
import 'package:rxdart/rxdart.dart';
part 'news.rxb.g.dart';
abstract class NewsBlocEvents {
void refresh({required final bool mainArticlesToo});
void addFeed(final String url, final int? folderID);
void removeFeed(final int feedID);
void renameFeed(final int feedID, final String name);
void moveFeed(final int feedID, final int? folderID);
void markFeedAsRead(final int feedID);
void createFolder(final String name);
void deleteFolder(final int folderID);
void renameFolder(final int folderID, final String name);
void markFolderAsRead(final int folderID);
}
abstract class NewsBlocStates {
BehaviorSubject<Result<List<NewsFolder>>> get folders;
BehaviorSubject<Result<List<NewsFeed>>> get feeds;
Stream<Exception> get errors;
BehaviorSubject<int> get unreadCounter;
}
@RxBloc()
class NewsBloc extends $NewsBloc {
NewsBloc(
this.options,
this.requestManager,
this.client,
) {
_$refreshEvent.listen(_loadAll);
_$addFeedEvent.listen((final event) {
_wrapFeedAction(
(final client) async => client.news.addFeed(
NewsAddFeed(
url: event.url,
folderId: event.folderID,
),
),
);
});
_$removeFeedEvent.listen((final feedID) {
_wrapFeedAction((final client) async => client.news.deleteFeed(feedID));
});
_$renameFeedEvent.listen((final event) {
_wrapFeedAction(
(final client) async => client.news.renameFeed(
event.feedID,
NewsRenameFeed(
feedTitle: event.name,
),
),
);
});
_$moveFeedEvent.listen((final event) {
final stream = requestManager
.wrapWithoutCache(
() async => client.news.moveFeed(
event.feedID,
NewsMoveFeed(
folderId: event.folderID,
),
),
)
.asBroadcastStream();
stream.whereError().listen(_errorsStreamController.add);
stream.whereSuccess().listen((final _) {
_loadFeeds();
_loadFolders();
});
});
_$markFeedAsReadEvent.listen((final feedID) {
final stream = requestManager
.wrapWithoutCache(
() async => client.news.markFeedAsRead(
feedID,
NewsMarkAsRead(
newestItemId: _newestItemId,
),
),
)
.asBroadcastStream();
stream.whereError().listen(_errorsStreamController.add);
stream.whereSuccess().listen((final _) {
_loadAll(true);
});
});
_$createFolderEvent.listen((final name) {
_wrapFolderAction((final client) async => client.news.createFolder(NewsCreateFolder(name: name)));
});
_$deleteFolderEvent.listen((final folderID) {
_wrapFolderAction((final client) async => client.news.deleteFolder(folderID));
});
_$renameFolderEvent.listen((final event) {
_wrapFolderAction(
(final client) async => client.news.renameFolder(
event.folderID,
NewsRenameFolder(
name: event.name,
),
),
);
});
_$markFolderAsReadEvent.listen((final folderID) {
final stream = requestManager
.wrapWithoutCache(
() async => client.news.markFolderAsRead(
folderID,
NewsMarkAsRead(
newestItemId: _newestItemId,
),
),
)
.asBroadcastStream();
stream.whereError().listen(_errorsStreamController.add);
stream.whereSuccess().listen((final _) {
_loadAll(true);
});
});
mainArticlesBloc.articles.listen((final result) {
if (result.data != null) {
final type = mainArticlesBloc.filterType.valueOrNull;
_unreadCounterSubject
.add(result.data!.where((final a) => type == FilterType.starred ? a.starred! : a.unread!).length);
}
});
_loadAll(false);
}
void _wrapFolderAction(final Future Function(NextcloudClient client) call) {
final stream = requestManager
.wrapWithoutCache(
() async => call(client),
)
.asBroadcastStream();
stream.whereError().listen(_errorsStreamController.add);
stream.whereSuccess().listen((final _) {
_loadFolders();
});
}
void _wrapFeedAction(final Future Function(NextcloudClient client) call) {
final stream = requestManager
.wrapWithoutCache(
() async => call(client),
)
.asBroadcastStream();
stream.whereError().listen(_errorsStreamController.add);
stream.whereSuccess().listen((final _) {
_loadFeeds();
mainArticlesBloc.loadArticles();
});
}
void _loadAll(final bool mainArticlesToo) {
if (mainArticlesToo) {
mainArticlesBloc.loadArticles();
}
_loadFolders();
_loadFeeds();
}
void _loadFolders() {
requestManager
.wrapNextcloud<List<NewsFolder>, NewsListFolders, void, NextcloudNewsClient>(
client.id,
client.news,
'news-folders',
() async => (await client.news.listFolders())!,
(final response) => response.folders,
previousData: _foldersSubject.valueOrNull?.data,
)
.listen(_foldersSubject.add);
}
void _loadFeeds() {
requestManager.wrapNextcloud<List<NewsFeed>, NewsListFeeds, void, NextcloudNewsClient>(
client.id,
client.news,
'news-feeds',
() async => (await client.news.listFeeds())!,
(final response) {
// This is a bit ugly, but IDGAF right now
_newestItemId = response.newestItemId;
return response.feeds;
},
previousData: _feedsSubject.valueOrNull?.data,
).listen(_feedsSubject.add);
}
final NewsAppSpecificOptions options;
final RequestManager requestManager;
final NextcloudClient client;
late final mainArticlesBloc = NewsArticlesBloc(
this,
isMainArticlesBloc: true,
);
int? _newestItemId;
final _foldersSubject = BehaviorSubject<Result<List<NewsFolder>>>();
final _feedsSubject = BehaviorSubject<Result<List<NewsFeed>>>();
final _errorsStreamController = StreamController<Exception>();
final _unreadCounterSubject = BehaviorSubject<int>();
@override
void dispose() {
_foldersSubject.close();
_feedsSubject.close();
_errorsStreamController.close();
_unreadCounterSubject.close();
super.dispose();
}
@override
BehaviorSubject<Result<List<NewsFeed>>> _mapToFeedsState() => _feedsSubject;
@override
BehaviorSubject<Result<List<NewsFolder>>> _mapToFoldersState() => _foldersSubject;
@override
Stream<Exception> _mapToErrorsState() => _errorsStreamController.stream.asBroadcastStream();
@override
BehaviorSubject<int> _mapToUnreadCounterState() => _unreadCounterSubject;
}