Browse Source

Merge pull request #112 from jld3103/fix/article-and-note-blocs

Fix article and note blocs
pull/113/head
jld3103 2 years ago committed by GitHub
parent
commit
ba4ea3a680
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  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. 74
      packages/neon/lib/src/apps/news/pages/article.dart
  7. 18
      packages/neon/lib/src/apps/news/widgets/articles_view.dart
  8. 4
      packages/neon/lib/src/apps/notes/app.dart
  9. 116
      packages/neon/lib/src/apps/notes/blocs/note.dart
  10. 120
      packages/neon/lib/src/apps/notes/blocs/note.rxb.g.dart
  11. 10
      packages/neon/lib/src/apps/notes/blocs/notes.dart
  12. 8
      packages/neon/lib/src/apps/notes/blocs/notes.rxb.g.dart
  13. 8
      packages/neon/lib/src/apps/notes/dialogs/select_category.dart
  14. 105
      packages/neon/lib/src/apps/notes/pages/note.dart
  15. 10
      packages/neon/lib/src/apps/notes/widgets/notes_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:html/parser.dart' as html_parser;
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:neon/l10n/localizations.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/articles.dart';
import 'package:neon/src/apps/news/blocs/news.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/blocs/apps.dart';
import 'package:neon/src/neon.dart'; import 'package:neon/src/neon.dart';
import 'package:nextcloud/nextcloud.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; BehaviorSubject<FilterType> get filterType;
Stream<NewsArticle> get articleUpdate;
Stream<Exception> get errors; Stream<Exception> get errors;
} }
@ -71,36 +69,24 @@ class NewsArticlesBloc extends $NewsArticlesBloc {
_$markArticleAsReadEvent.listen((final article) { _$markArticleAsReadEvent.listen((final article) {
_wrapArticleAction((final client) async { _wrapArticleAction((final client) async {
await client.news.markArticleAsRead(itemId: article.id); await client.news.markArticleAsRead(itemId: article.id);
// TODO
//_articleUpdateController.add(article..unread = false);
}); });
}); });
_$markArticleAsUnreadEvent.listen((final article) { _$markArticleAsUnreadEvent.listen((final article) {
_wrapArticleAction((final client) async { _wrapArticleAction((final client) async {
await client.news.markArticleAsUnread(itemId: article.id); await client.news.markArticleAsUnread(itemId: article.id);
// TODO
//_articleUpdateController.add(article..unread = true);
}); });
}); });
_$starArticleEvent.listen((final article) { _$starArticleEvent.listen((final article) {
_wrapArticleAction((final client) async { _wrapArticleAction((final client) async {
await client.news.starArticle( await client.news.starArticle(itemId: article.id);
itemId: article.id,
);
// TODO
//_articleUpdateController.add(article..starred = true);
}); });
}); });
_$unstarArticleEvent.listen((final article) { _$unstarArticleEvent.listen((final article) {
_wrapArticleAction((final client) async { _wrapArticleAction((final client) async {
await client.news.unstarArticle( await client.news.unstarArticle(itemId: article.id);
itemId: article.id,
);
// TODO
//_articleUpdateController.add(article..starred = false);
}); });
}); });
@ -184,14 +170,12 @@ class NewsArticlesBloc extends $NewsArticlesBloc {
final _articlesSubject = BehaviorSubject<Result<List<NewsArticle>>>(); final _articlesSubject = BehaviorSubject<Result<List<NewsArticle>>>();
late final BehaviorSubject<FilterType> _filterTypeSubject; late final BehaviorSubject<FilterType> _filterTypeSubject;
final _articleUpdateController = StreamController<NewsArticle>();
final _errorsStreamController = StreamController<Exception>(); final _errorsStreamController = StreamController<Exception>();
@override @override
void dispose() { void dispose() {
unawaited(_articlesSubject.close()); unawaited(_articlesSubject.close());
unawaited(_filterTypeSubject.close()); unawaited(_filterTypeSubject.close());
unawaited(_articleUpdateController.close());
unawaited(_errorsStreamController.close()); unawaited(_errorsStreamController.close());
super.dispose(); super.dispose();
} }
@ -202,9 +186,6 @@ class NewsArticlesBloc extends $NewsArticlesBloc {
@override @override
BehaviorSubject<FilterType> _mapToFilterTypeState() => _filterTypeSubject; BehaviorSubject<FilterType> _mapToFilterTypeState() => _filterTypeSubject;
@override
Stream<NewsArticle> _mapToArticleUpdateState() => _articleUpdateController.stream.asBroadcastStream();
@override @override
Stream<Exception> _mapToErrorsState() => _errorsStreamController.stream.asBroadcastStream(); 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] /// The state of [filterType] implemented in [_mapToFilterTypeState]
late final BehaviorSubject<FilterType> _filterTypeState = _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] /// The state of [errors] implemented in [_mapToErrorsState]
late final Stream<Exception> _errorsState = _mapToErrorsState(); late final Stream<Exception> _errorsState = _mapToErrorsState();
@ -73,9 +70,6 @@ abstract class $NewsArticlesBloc extends RxBlocBase
@override @override
BehaviorSubject<FilterType> get filterType => _filterTypeState; BehaviorSubject<FilterType> get filterType => _filterTypeState;
@override
Stream<NewsArticle> get articleUpdate => _articleUpdateState;
@override @override
Stream<Exception> get errors => _errorsState; Stream<Exception> get errors => _errorsState;
@ -83,8 +77,6 @@ abstract class $NewsArticlesBloc extends RxBlocBase
BehaviorSubject<FilterType> _mapToFilterTypeState(); BehaviorSubject<FilterType> _mapToFilterTypeState();
Stream<NewsArticle> _mapToArticleUpdateState();
Stream<Exception> _mapToErrorsState(); Stream<Exception> _mapToErrorsState();
@override @override

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

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

4
packages/neon/lib/src/apps/notes/app.dart

@ -1,13 +1,17 @@
library notes; library notes;
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_rx_bloc/flutter_rx_bloc.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:neon/l10n/localizations.dart'; import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/apps/notes/blocs/note.dart';
import 'package:neon/src/apps/notes/blocs/notes.dart'; import 'package:neon/src/apps/notes/blocs/notes.dart';
import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/blocs/apps.dart'; import 'package:neon/src/blocs/apps.dart';
import 'package:neon/src/neon.dart'; import 'package:neon/src/neon.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';

116
packages/neon/lib/src/apps/notes/blocs/note.dart

@ -0,0 +1,116 @@
import 'dart:async';
import 'package:neon/src/apps/notes/app.dart';
import 'package:neon/src/apps/notes/blocs/notes.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 'note.rxb.g.dart';
abstract class NotesNoteBlocEvents {
void updateNote(
final int id,
final String etag, {
final String? title,
final String? category,
final String? content,
final bool? favorite,
});
}
abstract class NotesNoteBlocStates {
BehaviorSubject<String> get content;
BehaviorSubject<String> get title;
BehaviorSubject<String> get category;
BehaviorSubject<String> get etag;
Stream<Exception> get errors;
}
@RxBloc()
class NotesNoteBloc extends $NotesNoteBloc {
NotesNoteBloc(
this.options,
this._requestManager,
this._client,
this._notesBloc,
final NotesNote note,
) {
_$updateNoteEvent.listen((final event) {
_wrapAction(
() async {
_emitNote(
await _client.notes.updateNote(
id: event.id,
title: event.title,
category: event.category,
content: event.content,
favorite: event.favorite ?? false ? 1 : 0,
ifMatch: '"${event.etag}"',
),
);
},
);
});
_emitNote(note);
id = note.id;
}
void _emitNote(final NotesNote note) {
_contentSubject.add(note.content);
_titleSubject.add(note.title);
_categorySubject.add(note.category);
_etagSubject.add(note.etag);
}
void _wrapAction(final Future Function() call) {
final stream = _requestManager.wrapWithoutCache(call).asBroadcastStream();
stream.whereError().listen(_errorsStreamController.add);
stream.whereSuccess().listen((final _) async {
_notesBloc.refresh();
});
}
final NotesAppSpecificOptions options;
final RequestManager _requestManager;
final NextcloudClient _client;
final NotesBloc _notesBloc;
late final int id;
final _contentSubject = BehaviorSubject<String>();
final _titleSubject = BehaviorSubject<String>();
final _categorySubject = BehaviorSubject<String>();
final _etagSubject = BehaviorSubject<String>();
final _errorsStreamController = StreamController<Exception>();
@override
void dispose() {
unawaited(_contentSubject.close());
unawaited(_titleSubject.close());
unawaited(_categorySubject.close());
unawaited(_etagSubject.close());
unawaited(_errorsStreamController.close());
super.dispose();
}
@override
Stream<Exception> _mapToErrorsState() => _errorsStreamController.stream.asBroadcastStream();
@override
BehaviorSubject<String> _mapToContentState() => _contentSubject;
@override
BehaviorSubject<String> _mapToTitleState() => _titleSubject;
@override
BehaviorSubject<String> _mapToCategoryState() => _categorySubject;
@override
BehaviorSubject<String> _mapToEtagState() => _etagSubject;
}

120
packages/neon/lib/src/apps/notes/blocs/note.rxb.g.dart

@ -0,0 +1,120 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// Generator: RxBlocGeneratorForAnnotation
// **************************************************************************
part of 'note.dart';
/// Used as a contractor for the bloc, events and states classes
/// {@nodoc}
abstract class NotesNoteBlocType extends RxBlocTypeBase {
NotesNoteBlocEvents get events;
NotesNoteBlocStates get states;
}
/// [$NotesNoteBloc] extended by the [NotesNoteBloc]
/// {@nodoc}
abstract class $NotesNoteBloc extends RxBlocBase
implements NotesNoteBlocEvents, NotesNoteBlocStates, NotesNoteBlocType {
final _compositeSubscription = CompositeSubscription();
/// Тhe [Subject] where events sink to by calling [updateNote]
final _$updateNoteEvent = PublishSubject<_UpdateNoteEventArgs>();
/// The state of [content] implemented in [_mapToContentState]
late final BehaviorSubject<String> _contentState = _mapToContentState();
/// The state of [title] implemented in [_mapToTitleState]
late final BehaviorSubject<String> _titleState = _mapToTitleState();
/// The state of [category] implemented in [_mapToCategoryState]
late final BehaviorSubject<String> _categoryState = _mapToCategoryState();
/// The state of [etag] implemented in [_mapToEtagState]
late final BehaviorSubject<String> _etagState = _mapToEtagState();
/// The state of [errors] implemented in [_mapToErrorsState]
late final Stream<Exception> _errorsState = _mapToErrorsState();
@override
void updateNote(
int id,
String etag, {
String? title,
String? category,
String? content,
bool? favorite,
}) =>
_$updateNoteEvent.add(_UpdateNoteEventArgs(
id,
etag,
title: title,
category: category,
content: content,
favorite: favorite,
));
@override
BehaviorSubject<String> get content => _contentState;
@override
BehaviorSubject<String> get title => _titleState;
@override
BehaviorSubject<String> get category => _categoryState;
@override
BehaviorSubject<String> get etag => _etagState;
@override
Stream<Exception> get errors => _errorsState;
BehaviorSubject<String> _mapToContentState();
BehaviorSubject<String> _mapToTitleState();
BehaviorSubject<String> _mapToCategoryState();
BehaviorSubject<String> _mapToEtagState();
Stream<Exception> _mapToErrorsState();
@override
NotesNoteBlocEvents get events => this;
@override
NotesNoteBlocStates get states => this;
@override
void dispose() {
_$updateNoteEvent.close();
_compositeSubscription.dispose();
super.dispose();
}
}
/// Helps providing the arguments in the [Subject.add] for
/// [NotesNoteBlocEvents.updateNote] event
class _UpdateNoteEventArgs {
const _UpdateNoteEventArgs(
this.id,
this.etag, {
this.title,
this.category,
this.content,
this.favorite,
});
final int id;
final String etag;
final String? title;
final String? category;
final String? content;
final bool? favorite;
}

10
packages/neon/lib/src/apps/notes/blocs/notes.dart

@ -32,8 +32,6 @@ abstract class NotesBlocEvents {
abstract class NotesBlocStates { abstract class NotesBlocStates {
BehaviorSubject<Result<List<NotesNote>>> get notes; BehaviorSubject<Result<List<NotesNote>>> get notes;
Stream<NotesNote> get noteUpdate;
Stream<Exception> get errors; Stream<Exception> get errors;
} }
@ -57,8 +55,7 @@ class NotesBloc extends $NotesBloc {
_$updateNoteEvent.listen((final event) { _$updateNoteEvent.listen((final event) {
_wrapAction( _wrapAction(
() async => _noteUpdateController.add( () async => client.notes.updateNote(
await client.notes.updateNote(
id: event.id, id: event.id,
title: event.title, title: event.title,
category: event.category, category: event.category,
@ -66,7 +63,6 @@ class NotesBloc extends $NotesBloc {
favorite: event.favorite ?? false ? 1 : 0, favorite: event.favorite ?? false ? 1 : 0,
ifMatch: '"${event.etag}"', ifMatch: '"${event.etag}"',
), ),
),
); );
}); });
@ -102,7 +98,6 @@ class NotesBloc extends $NotesBloc {
final NextcloudClient client; final NextcloudClient client;
final _notesSubject = BehaviorSubject<Result<List<NotesNote>>>(); final _notesSubject = BehaviorSubject<Result<List<NotesNote>>>();
final _noteUpdateController = StreamController<NotesNote>();
final _errorsStreamController = StreamController<Exception>(); final _errorsStreamController = StreamController<Exception>();
@override @override
@ -115,9 +110,6 @@ class NotesBloc extends $NotesBloc {
@override @override
BehaviorSubject<Result<List<NotesNote>>> _mapToNotesState() => _notesSubject; BehaviorSubject<Result<List<NotesNote>>> _mapToNotesState() => _notesSubject;
@override
Stream<NotesNote> _mapToNoteUpdateState() => _noteUpdateController.stream.asBroadcastStream();
@override @override
Stream<Exception> _mapToErrorsState() => _errorsStreamController.stream.asBroadcastStream(); Stream<Exception> _mapToErrorsState() => _errorsStreamController.stream.asBroadcastStream();
} }

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

@ -33,9 +33,6 @@ abstract class $NotesBloc extends RxBlocBase implements NotesBlocEvents, NotesBl
/// The state of [notes] implemented in [_mapToNotesState] /// The state of [notes] implemented in [_mapToNotesState]
late final BehaviorSubject<Result<List<NotesNote>>> _notesState = _mapToNotesState(); late final BehaviorSubject<Result<List<NotesNote>>> _notesState = _mapToNotesState();
/// The state of [noteUpdate] implemented in [_mapToNoteUpdateState]
late final Stream<NotesNote> _noteUpdateState = _mapToNoteUpdateState();
/// The state of [errors] implemented in [_mapToErrorsState] /// The state of [errors] implemented in [_mapToErrorsState]
late final Stream<Exception> _errorsState = _mapToErrorsState(); late final Stream<Exception> _errorsState = _mapToErrorsState();
@ -76,16 +73,11 @@ abstract class $NotesBloc extends RxBlocBase implements NotesBlocEvents, NotesBl
@override @override
BehaviorSubject<Result<List<NotesNote>>> get notes => _notesState; BehaviorSubject<Result<List<NotesNote>>> get notes => _notesState;
@override
Stream<NotesNote> get noteUpdate => _noteUpdateState;
@override @override
Stream<Exception> get errors => _errorsState; Stream<Exception> get errors => _errorsState;
BehaviorSubject<Result<List<NotesNote>>> _mapToNotesState(); BehaviorSubject<Result<List<NotesNote>>> _mapToNotesState();
Stream<NotesNote> _mapToNoteUpdateState();
Stream<Exception> _mapToErrorsState(); Stream<Exception> _mapToErrorsState();
@override @override

8
packages/neon/lib/src/apps/notes/dialogs/select_category.dart

@ -3,12 +3,12 @@ part of '../app.dart';
class NotesSelectCategoryDialog extends StatefulWidget { class NotesSelectCategoryDialog extends StatefulWidget {
const NotesSelectCategoryDialog({ const NotesSelectCategoryDialog({
required this.bloc, required this.bloc,
required this.note, this.initialCategory,
super.key, super.key,
}); });
final NotesBloc bloc; final NotesBloc bloc;
final NotesNote note; final String? initialCategory;
@override @override
State<NotesSelectCategoryDialog> createState() => _NotesSelectCategoryDialogState(); State<NotesSelectCategoryDialog> createState() => _NotesSelectCategoryDialogState();
@ -21,7 +21,7 @@ class _NotesSelectCategoryDialogState extends State<NotesSelectCategoryDialog> {
void submit() { void submit() {
if (formKey.currentState!.validate()) { if (formKey.currentState!.validate()) {
Navigator.of(context).pop(selectedCategory ?? widget.note.category); Navigator.of(context).pop(selectedCategory);
} }
} }
@ -60,7 +60,7 @@ class _NotesSelectCategoryDialogState extends State<NotesSelectCategoryDialog> {
if (notesData != null) ...[ if (notesData != null) ...[
NotesCategorySelect( NotesCategorySelect(
categories: notesData.map((final note) => note.category).toSet().toList(), categories: notesData.map((final note) => note.category).toSet().toList(),
initialValue: widget.note.category, initialValue: widget.initialCategory,
onChanged: (final category) { onChanged: (final category) {
selectedCategory = category; selectedCategory = category;
}, },

105
packages/neon/lib/src/apps/notes/pages/note.dart

@ -3,41 +3,41 @@ part of '../app.dart';
class NotesNotePage extends StatefulWidget { class NotesNotePage extends StatefulWidget {
const NotesNotePage({ const NotesNotePage({
required this.bloc, required this.bloc,
required this.note, required this.notesBloc,
super.key, super.key,
}); });
final NotesBloc bloc; final NotesNoteBloc bloc;
final NotesNote note; final NotesBloc notesBloc;
@override @override
State<NotesNotePage> createState() => _NotesNotePageState(); State<NotesNotePage> createState() => _NotesNotePageState();
} }
class _NotesNotePageState extends State<NotesNotePage> { class _NotesNotePageState extends State<NotesNotePage> {
late final _contentController = TextEditingController(text: widget.note.content); late final _contentController = TextEditingController();
late final _titleController = TextEditingController(text: widget.note.title); late final _titleController = TextEditingController();
final _contentFocusNode = FocusNode(); final _contentFocusNode = FocusNode();
final _titleFocusNode = FocusNode(); final _titleFocusNode = FocusNode();
final _updateController = StreamController();
late NotesNote _note = widget.note;
bool _showEditor = false; bool _showEditor = false;
bool _synced = true;
void _focusEditor() { void _focusEditor() {
_contentFocusNode.requestFocus(); _contentFocusNode.requestFocus();
_contentController.selection = TextSelection.collapsed(offset: _contentController.text.length); _contentController.selection = TextSelection.collapsed(offset: _contentController.text.length);
} }
void _update([final String? selectedCategory]) { Future _update({
final updatedTitle = _note.title != _titleController.text ? _titleController.text : null; final String? category,
final updatedCategory = selectedCategory != null && _note.category != selectedCategory ? selectedCategory : null; }) async {
final updatedContent = _note.content != _contentController.text ? _contentController.text : null; final updatedTitle = await widget.bloc.title.first != _titleController.text ? _titleController.text : null;
final updatedCategory = category != null && await widget.bloc.category.first != category ? category : null;
final updatedContent = await widget.bloc.content.first != _contentController.text ? _contentController.text : null;
if (updatedTitle != null || updatedCategory != null || updatedContent != null) { if (updatedTitle != null || updatedCategory != null || updatedContent != null) {
widget.bloc.updateNote( widget.bloc.updateNote(
_note.id, widget.bloc.id,
_note.etag, await widget.bloc.etag.first,
title: updatedTitle, title: updatedTitle,
category: updatedCategory, category: updatedCategory,
content: updatedContent, content: updatedContent,
@ -49,33 +49,28 @@ class _NotesNotePageState extends State<NotesNotePage> {
void initState() { void initState() {
super.initState(); super.initState();
void updateSynced() { widget.bloc.errors.listen((final error) {
_synced = _note.content == _contentController.text; handleNotesException(context, error);
}
_contentController.addListener(() => setState(updateSynced));
widget.bloc.noteUpdate.listen((final n) {
if (mounted && n.id == _note.id) {
setState(() {
_note = n;
updateSynced();
}); });
}
widget.bloc.content.listen((final content) {
_contentController.text = content;
}); });
_titleFocusNode.addListener(() { widget.bloc.title.listen((final title) {
if (!_titleFocusNode.hasFocus) { _titleController.text = title;
_update();
}
}); });
_contentController.addListener(() => _updateController.add(null));
_titleController.addListener(() => _updateController.add(null));
_updateController.stream.debounceTime(const Duration(seconds: 1)).listen((final _) async => _update());
WidgetsBinding.instance.addPostFrameCallback((final _) async { WidgetsBinding.instance.addPostFrameCallback((final _) async {
if (Provider.of<NeonPlatform>(context, listen: false).canUseWakelock) { if (Provider.of<NeonPlatform>(context, listen: false).canUseWakelock) {
await Wakelock.enable(); await Wakelock.enable();
} }
if (widget.bloc.options.defaultNoteViewTypeOption.value == DefaultNoteViewType.edit || if (widget.bloc.options.defaultNoteViewTypeOption.value == DefaultNoteViewType.edit ||
widget.note.content.isEmpty) { (await widget.bloc.content.first).isEmpty) {
setState(() { setState(() {
_showEditor = true; _showEditor = true;
}); });
@ -84,11 +79,20 @@ class _NotesNotePageState extends State<NotesNotePage> {
}); });
} }
@override
void dispose() {
unawaited(_updateController.close());
super.dispose();
}
@override @override
Widget build(final BuildContext context) => WillPopScope( Widget build(final BuildContext context) => WillPopScope(
onWillPop: () async { onWillPop: () async {
_update(); await _update();
if (!mounted) {
return true;
}
if (Provider.of<NeonPlatform>(context, listen: false).canUseWakelock) { if (Provider.of<NeonPlatform>(context, listen: false).canUseWakelock) {
await Wakelock.disable(); await Wakelock.disable();
} }
@ -112,12 +116,6 @@ class _NotesNotePageState extends State<NotesNotePage> {
), ),
), ),
actions: [ actions: [
IconButton(
icon: Icon(
_synced ? Icons.check : Icons.sync,
),
onPressed: _update,
),
IconButton( IconButton(
icon: Icon( icon: Icon(
_showEditor ? Icons.visibility : Icons.edit, _showEditor ? Icons.visibility : Icons.edit,
@ -135,23 +133,33 @@ class _NotesNotePageState extends State<NotesNotePage> {
} }
}, },
), ),
IconButton( RxBlocBuilder(
bloc: widget.bloc,
state: (final bloc) => bloc.category,
builder: (final context, final categorySnapshot, final _) {
final category = categorySnapshot.data ?? '';
return IconButton(
onPressed: () async { onPressed: () async {
final result = await showDialog<String>( final result = await showDialog<String>(
context: context, context: context,
builder: (final context) => NotesSelectCategoryDialog( builder: (final context) => NotesSelectCategoryDialog(
bloc: widget.bloc, bloc: widget.notesBloc,
note: _note, initialCategory: category,
), ),
); );
if (result != null) { if (result != null) {
_update(result); await _update(
category: result,
);
} }
}, },
icon: Icon( icon: Icon(
MdiIcons.tag, MdiIcons.tag,
color: _note.category.isNotEmpty ? NotesCategoryColor.compute(_note.category) : null, color: category.isNotEmpty ? NotesCategoryColor.compute(category) : null,
), ),
);
},
), ),
], ],
), ),
@ -178,8 +186,13 @@ class _NotesNotePageState extends State<NotesNotePage> {
border: InputBorder.none, border: InputBorder.none,
), ),
) )
: MarkdownBody( : RxBlocBuilder(
data: _contentController.text, bloc: widget.bloc,
state: (final bloc) => bloc.content,
builder: (final context, final contentSnapshot, final _) {
final content = contentSnapshot.data ?? '';
return MarkdownBody(
data: content,
onTapLink: (final text, final href, final title) async { onTapLink: (final text, final href, final title) async {
if (href != null) { if (href != null) {
await launchUrlString( await launchUrlString(
@ -188,6 +201,8 @@ class _NotesNotePageState extends State<NotesNotePage> {
); );
} }
}, },
);
},
), ),
), ),
), ),

10
packages/neon/lib/src/apps/notes/widgets/notes_view.dart

@ -125,8 +125,14 @@ class NotesView extends StatelessWidget {
await Navigator.of(context).push( await Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (final context) => NotesNotePage( builder: (final context) => NotesNotePage(
bloc: bloc, bloc: NotesNoteBloc(
note: note, bloc.options,
Provider.of<RequestManager>(context, listen: false),
RxBlocProvider.of<AccountsBloc>(context).activeAccount.value!.client,
bloc,
note,
),
notesBloc: bloc,
), ),
), ),
); );

Loading…
Cancel
Save