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. 86
      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. 22
      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. 137
      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: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,
),
),

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

@ -1,13 +1,17 @@
library notes;
import 'dart:async';
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.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: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/blocs/accounts.dart';
import 'package:neon/src/blocs/apps.dart';
import 'package:neon/src/neon.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;
}

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

@ -32,8 +32,6 @@ abstract class NotesBlocEvents {
abstract class NotesBlocStates {
BehaviorSubject<Result<List<NotesNote>>> get notes;
Stream<NotesNote> get noteUpdate;
Stream<Exception> get errors;
}
@ -57,15 +55,13 @@ class NotesBloc extends $NotesBloc {
_$updateNoteEvent.listen((final event) {
_wrapAction(
() async => _noteUpdateController.add(
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}"',
),
() async => client.notes.updateNote(
id: event.id,
title: event.title,
category: event.category,
content: event.content,
favorite: event.favorite ?? false ? 1 : 0,
ifMatch: '"${event.etag}"',
),
);
});
@ -102,7 +98,6 @@ class NotesBloc extends $NotesBloc {
final NextcloudClient client;
final _notesSubject = BehaviorSubject<Result<List<NotesNote>>>();
final _noteUpdateController = StreamController<NotesNote>();
final _errorsStreamController = StreamController<Exception>();
@override
@ -115,9 +110,6 @@ class NotesBloc extends $NotesBloc {
@override
BehaviorSubject<Result<List<NotesNote>>> _mapToNotesState() => _notesSubject;
@override
Stream<NotesNote> _mapToNoteUpdateState() => _noteUpdateController.stream.asBroadcastStream();
@override
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]
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]
late final Stream<Exception> _errorsState = _mapToErrorsState();
@ -76,16 +73,11 @@ abstract class $NotesBloc extends RxBlocBase implements NotesBlocEvents, NotesBl
@override
BehaviorSubject<Result<List<NotesNote>>> get notes => _notesState;
@override
Stream<NotesNote> get noteUpdate => _noteUpdateState;
@override
Stream<Exception> get errors => _errorsState;
BehaviorSubject<Result<List<NotesNote>>> _mapToNotesState();
Stream<NotesNote> _mapToNoteUpdateState();
Stream<Exception> _mapToErrorsState();
@override

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

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

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

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

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

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

Loading…
Cancel
Save