Browse Source

neon: Implement proper article bloc

pull/112/head
jld3103 2 years ago
parent
commit
ec036772cd
No known key found for this signature in database
GPG Key ID: 9062417B9E8EB7B3
  1. 2
      packages/neon/lib/src/apps/news/app.dart
  2. 103
      packages/neon/lib/src/apps/news/blocs/article.dart
  3. 85
      packages/neon/lib/src/apps/news/blocs/article.rxb.g.dart
  4. 23
      packages/neon/lib/src/apps/news/blocs/articles.dart
  5. 8
      packages/neon/lib/src/apps/news/blocs/articles.rxb.g.dart
  6. 86
      packages/neon/lib/src/apps/news/pages/article.dart
  7. 18
      packages/neon/lib/src/apps/news/widgets/articles_view.dart

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

@ -10,8 +10,10 @@ import 'package:html/dom.dart' as html_dom;
import 'package:html/parser.dart' as html_parser;
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/apps/news/blocs/article.dart';
import 'package:neon/src/apps/news/blocs/articles.dart';
import 'package:neon/src/apps/news/blocs/news.dart';
import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/blocs/apps.dart';
import 'package:neon/src/neon.dart';
import 'package:nextcloud/nextcloud.dart';

103
packages/neon/lib/src/apps/news/blocs/article.dart

@ -0,0 +1,103 @@
import 'dart:async';
import 'package:neon/src/apps/news/blocs/articles.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 'article.rxb.g.dart';
abstract class NewsArticleBlocEvents {
void markArticleAsRead();
void markArticleAsUnread();
void starArticle();
void unstarArticle();
}
abstract class NewsArticleBlocStates {
BehaviorSubject<bool> get unread;
BehaviorSubject<bool> get starred;
Stream<Exception> get errors;
}
@RxBloc()
class NewsArticleBloc extends $NewsArticleBloc {
NewsArticleBloc(
this._requestManager,
this._client,
this._newsArticlesBloc,
final NewsArticle article,
) {
_$markArticleAsReadEvent.listen((final _) {
_wrapArticleAction(() async {
await _client.news.markArticleAsRead(itemId: article.id);
_unreadSubject.add(false);
});
});
_$markArticleAsUnreadEvent.listen((final _) {
_wrapArticleAction(() async {
await _client.news.markArticleAsUnread(itemId: article.id);
_unreadSubject.add(true);
});
});
_$starArticleEvent.listen((final _) {
_wrapArticleAction(() async {
await _client.news.starArticle(itemId: article.id);
_starredSubject.add(true);
});
});
_$unstarArticleEvent.listen((final _) {
_wrapArticleAction(() async {
await _client.news.unstarArticle(itemId: article.id);
_starredSubject.add(false);
});
});
_unreadSubject.add(article.unread);
_starredSubject.add(article.starred);
url = article.url;
}
void _wrapArticleAction(final Future Function() call) {
final stream = _requestManager.wrapWithoutCache(() async => call()).asBroadcastStream();
stream.whereError().listen(_errorsStreamController.add);
stream.whereSuccess().listen((final _) async {
_newsArticlesBloc.refresh();
});
}
final RequestManager _requestManager;
final NextcloudClient _client;
final NewsArticlesBloc _newsArticlesBloc;
late final String url;
final _unreadSubject = BehaviorSubject<bool>();
final _starredSubject = BehaviorSubject<bool>();
final _errorsStreamController = StreamController<Exception>();
@override
void dispose() {
unawaited(_unreadSubject.close());
unawaited(_starredSubject.close());
unawaited(_errorsStreamController.close());
super.dispose();
}
@override
BehaviorSubject<bool> _mapToUnreadState() => _unreadSubject;
@override
BehaviorSubject<bool> _mapToStarredState() => _starredSubject;
@override
Stream<Exception> _mapToErrorsState() => _errorsStreamController.stream.asBroadcastStream();
}

85
packages/neon/lib/src/apps/news/blocs/article.rxb.g.dart

@ -0,0 +1,85 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// Generator: RxBlocGeneratorForAnnotation
// **************************************************************************
part of 'article.dart';
/// Used as a contractor for the bloc, events and states classes
/// {@nodoc}
abstract class NewsArticleBlocType extends RxBlocTypeBase {
NewsArticleBlocEvents get events;
NewsArticleBlocStates get states;
}
/// [$NewsArticleBloc] extended by the [NewsArticleBloc]
/// {@nodoc}
abstract class $NewsArticleBloc extends RxBlocBase
implements NewsArticleBlocEvents, NewsArticleBlocStates, NewsArticleBlocType {
final _compositeSubscription = CompositeSubscription();
/// Тhe [Subject] where events sink to by calling [markArticleAsRead]
final _$markArticleAsReadEvent = PublishSubject<void>();
/// Тhe [Subject] where events sink to by calling [markArticleAsUnread]
final _$markArticleAsUnreadEvent = PublishSubject<void>();
/// Тhe [Subject] where events sink to by calling [starArticle]
final _$starArticleEvent = PublishSubject<void>();
/// Тhe [Subject] where events sink to by calling [unstarArticle]
final _$unstarArticleEvent = PublishSubject<void>();
/// The state of [unread] implemented in [_mapToUnreadState]
late final BehaviorSubject<bool> _unreadState = _mapToUnreadState();
/// The state of [starred] implemented in [_mapToStarredState]
late final BehaviorSubject<bool> _starredState = _mapToStarredState();
/// The state of [errors] implemented in [_mapToErrorsState]
late final Stream<Exception> _errorsState = _mapToErrorsState();
@override
void markArticleAsRead() => _$markArticleAsReadEvent.add(null);
@override
void markArticleAsUnread() => _$markArticleAsUnreadEvent.add(null);
@override
void starArticle() => _$starArticleEvent.add(null);
@override
void unstarArticle() => _$unstarArticleEvent.add(null);
@override
BehaviorSubject<bool> get unread => _unreadState;
@override
BehaviorSubject<bool> get starred => _starredState;
@override
Stream<Exception> get errors => _errorsState;
BehaviorSubject<bool> _mapToUnreadState();
BehaviorSubject<bool> _mapToStarredState();
Stream<Exception> _mapToErrorsState();
@override
NewsArticleBlocEvents get events => this;
@override
NewsArticleBlocStates get states => this;
@override
void dispose() {
_$markArticleAsReadEvent.close();
_$markArticleAsUnreadEvent.close();
_$starArticleEvent.close();
_$unstarArticleEvent.close();
_compositeSubscription.dispose();
super.dispose();
}
}

23
packages/neon/lib/src/apps/news/blocs/articles.dart

@ -39,8 +39,6 @@ abstract class NewsArticlesBlocStates {
BehaviorSubject<FilterType> get filterType;
Stream<NewsArticle> get articleUpdate;
Stream<Exception> get errors;
}
@ -71,36 +69,24 @@ class NewsArticlesBloc extends $NewsArticlesBloc {
_$markArticleAsReadEvent.listen((final article) {
_wrapArticleAction((final client) async {
await client.news.markArticleAsRead(itemId: article.id);
// TODO
//_articleUpdateController.add(article..unread = false);
});
});
_$markArticleAsUnreadEvent.listen((final article) {
_wrapArticleAction((final client) async {
await client.news.markArticleAsUnread(itemId: article.id);
// TODO
//_articleUpdateController.add(article..unread = true);
});
});
_$starArticleEvent.listen((final article) {
_wrapArticleAction((final client) async {
await client.news.starArticle(
itemId: article.id,
);
// TODO
//_articleUpdateController.add(article..starred = true);
await client.news.starArticle(itemId: article.id);
});
});
_$unstarArticleEvent.listen((final article) {
_wrapArticleAction((final client) async {
await client.news.unstarArticle(
itemId: article.id,
);
// TODO
//_articleUpdateController.add(article..starred = false);
await client.news.unstarArticle(itemId: article.id);
});
});
@ -184,14 +170,12 @@ class NewsArticlesBloc extends $NewsArticlesBloc {
final _articlesSubject = BehaviorSubject<Result<List<NewsArticle>>>();
late final BehaviorSubject<FilterType> _filterTypeSubject;
final _articleUpdateController = StreamController<NewsArticle>();
final _errorsStreamController = StreamController<Exception>();
@override
void dispose() {
unawaited(_articlesSubject.close());
unawaited(_filterTypeSubject.close());
unawaited(_articleUpdateController.close());
unawaited(_errorsStreamController.close());
super.dispose();
}
@ -202,9 +186,6 @@ class NewsArticlesBloc extends $NewsArticlesBloc {
@override
BehaviorSubject<FilterType> _mapToFilterTypeState() => _filterTypeSubject;
@override
Stream<NewsArticle> _mapToArticleUpdateState() => _articleUpdateController.stream.asBroadcastStream();
@override
Stream<Exception> _mapToErrorsState() => _errorsStreamController.stream.asBroadcastStream();
}

8
packages/neon/lib/src/apps/news/blocs/articles.rxb.g.dart

@ -43,9 +43,6 @@ abstract class $NewsArticlesBloc extends RxBlocBase
/// The state of [filterType] implemented in [_mapToFilterTypeState]
late final BehaviorSubject<FilterType> _filterTypeState = _mapToFilterTypeState();
/// The state of [articleUpdate] implemented in [_mapToArticleUpdateState]
late final Stream<NewsArticle> _articleUpdateState = _mapToArticleUpdateState();
/// The state of [errors] implemented in [_mapToErrorsState]
late final Stream<Exception> _errorsState = _mapToErrorsState();
@ -73,9 +70,6 @@ abstract class $NewsArticlesBloc extends RxBlocBase
@override
BehaviorSubject<FilterType> get filterType => _filterTypeState;
@override
Stream<NewsArticle> get articleUpdate => _articleUpdateState;
@override
Stream<Exception> get errors => _errorsState;
@ -83,8 +77,6 @@ abstract class $NewsArticlesBloc extends RxBlocBase
BehaviorSubject<FilterType> _mapToFilterTypeState();
Stream<NewsArticle> _mapToArticleUpdateState();
Stream<Exception> _mapToErrorsState();
@override

86
packages/neon/lib/src/apps/news/pages/article.dart

@ -3,14 +3,14 @@ part of '../app.dart';
class NewsArticlePage extends StatefulWidget {
const NewsArticlePage({
required this.bloc,
required this.article,
required this.articlesBloc,
required this.useWebView,
this.bodyData,
super.key,
}) : assert(useWebView || bodyData != null, 'bodyData has to be set when not using a WebView');
final NewsArticlesBloc bloc;
final NewsArticle article;
final NewsArticleBloc bloc;
final NewsArticlesBloc articlesBloc;
final bool useWebView;
final String? bodyData;
@ -19,8 +19,6 @@ class NewsArticlePage extends StatefulWidget {
}
class _NewsArticlePageState extends State<NewsArticlePage> {
late NewsArticle article = widget.article;
bool _webviewLoading = true;
WebViewController? _webviewController;
Timer? _markAsReadTimer;
@ -29,12 +27,8 @@ class _NewsArticlePageState extends State<NewsArticlePage> {
void initState() {
super.initState();
widget.bloc.articleUpdate.listen((final a) {
if (mounted && a.id == article.id) {
setState(() {
article = a;
});
}
widget.bloc.errors.listen((final error) {
ExceptionWidget.showSnackbar(context, error);
});
WidgetsBinding.instance.addPostFrameCallback((final _) async {
@ -44,7 +38,7 @@ class _NewsArticlePageState extends State<NewsArticlePage> {
});
if (!widget.useWebView) {
_startMarkAsReadTimer();
unawaited(_startMarkAsReadTimer());
}
}
@ -55,14 +49,14 @@ class _NewsArticlePageState extends State<NewsArticlePage> {
super.dispose();
}
void _startMarkAsReadTimer() {
if (article.unread) {
if (widget.bloc.newsBloc.options.articleDisableMarkAsReadTimeoutOption.value) {
widget.bloc.markArticleAsRead(article);
Future _startMarkAsReadTimer() async {
if (await widget.bloc.unread.first) {
if (widget.articlesBloc.newsBloc.options.articleDisableMarkAsReadTimeoutOption.value) {
widget.bloc.markArticleAsRead();
} else {
_markAsReadTimer = Timer(const Duration(seconds: 3), () {
if (article.unread) {
widget.bloc.markArticleAsRead(article);
_markAsReadTimer = Timer(const Duration(seconds: 3), () async {
if (await widget.bloc.unread.first) {
widget.bloc.markArticleAsRead();
}
});
}
@ -81,7 +75,7 @@ class _NewsArticlePageState extends State<NewsArticlePage> {
return (await _webviewController!.currentUrl())!;
}
return article.url;
return widget.bloc.url;
}
@override
@ -101,25 +95,39 @@ class _NewsArticlePageState extends State<NewsArticlePage> {
resizeToAvoidBottomInset: false,
appBar: AppBar(
actions: [
IconButton(
onPressed: () async {
if (article.starred) {
widget.bloc.unstarArticle(article);
} else {
widget.bloc.starArticle(article);
}
RxBlocBuilder(
bloc: widget.bloc,
state: (final bloc) => bloc.starred,
builder: (final context, final starredSnapshot, final _) {
final starred = starredSnapshot.data ?? false;
return IconButton(
onPressed: () async {
if (starred) {
widget.bloc.unstarArticle();
} else {
widget.bloc.starArticle();
}
},
icon: Icon(starred ? Icons.star : Icons.star_outline),
);
},
icon: Icon(article.starred ? Icons.star : Icons.star_outline),
),
IconButton(
onPressed: () async {
if (article.unread) {
widget.bloc.markArticleAsRead(article);
} else {
widget.bloc.markArticleAsUnread(article);
}
RxBlocBuilder(
bloc: widget.bloc,
state: (final bloc) => bloc.unread,
builder: (final context, final unreadSnapshot, final _) {
final unread = unreadSnapshot.data ?? false;
return IconButton(
onPressed: () async {
if (unread) {
widget.bloc.markArticleAsRead();
} else {
widget.bloc.markArticleAsUnread();
}
},
icon: Icon(unread ? MdiIcons.email : MdiIcons.emailMarkAsUnread),
);
},
icon: Icon(article.unread ? MdiIcons.email : MdiIcons.emailMarkAsUnread),
),
IconButton(
onPressed: () async {
@ -147,15 +155,15 @@ class _NewsArticlePageState extends State<NewsArticlePage> {
javascriptMode: JavascriptMode.unrestricted,
onWebViewCreated: (final controller) async {
_webviewController = controller;
await controller.loadUrl(article.url);
await controller.loadUrl(widget.bloc.url);
},
onPageStarted: (final _) {
setState(() {
_webviewLoading = true;
});
},
onPageFinished: (final _) {
_startMarkAsReadTimer();
onPageFinished: (final _) async {
await _startMarkAsReadTimer();
setState(() {
_webviewLoading = false;
});

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

@ -222,8 +222,13 @@ class _NewsArticlesViewState extends State<NewsArticlesView> {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (final context) => NewsArticlePage(
bloc: bloc,
article: article,
bloc: NewsArticleBloc(
Provider.of<RequestManager>(context, listen: false),
RxBlocProvider.of<AccountsBloc>(context).activeAccount.value!.client,
widget.bloc,
article,
),
articlesBloc: widget.bloc,
useWebView: false,
bodyData: bodyData,
),
@ -234,8 +239,13 @@ class _NewsArticlesViewState extends State<NewsArticlesView> {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (final context) => NewsArticlePage(
bloc: bloc,
article: article,
bloc: NewsArticleBloc(
Provider.of<RequestManager>(context, listen: false),
RxBlocProvider.of<AccountsBloc>(context).activeAccount.value!.client,
widget.bloc,
article,
),
articlesBloc: widget.bloc,
useWebView: true,
),
),

Loading…
Cancel
Save