Browse Source

Merge pull request #50 from jld3103/feature/app-unread-counter

Add app unread counter
pull/52/head
jld3103 2 years ago committed by GitHub
parent
commit
e850781112
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 37
      packages/neon/lib/src/apps/files/app.dart
  2. 37
      packages/neon/lib/src/apps/news/app.dart
  3. 15
      packages/neon/lib/src/apps/news/blocs/news.dart
  4. 8
      packages/neon/lib/src/apps/news/blocs/news.rxb.g.dart
  5. 36
      packages/neon/lib/src/apps/notes/app.dart
  6. 38
      packages/neon/lib/src/apps/notifications/app.dart
  7. 14
      packages/neon/lib/src/apps/notifications/blocs/notifications.dart
  8. 8
      packages/neon/lib/src/apps/notifications/blocs/notifications.rxb.g.dart
  9. 21
      packages/neon/lib/src/pages/home/home.dart
  10. 29
      packages/neon/lib/src/utils/app_implementation.dart

37
packages/neon/lib/src/apps/files/app.dart

@ -18,6 +18,7 @@ import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/apps/files/blocs/browser.dart'; import 'package:neon/src/apps/files/blocs/browser.dart';
import 'package:neon/src/apps/files/blocs/files.dart'; import 'package:neon/src/apps/files/blocs/files.dart';
import 'package:neon/src/blocs/accounts.dart'; import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/blocs/apps.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/neon.dart'; import 'package:neon/src/neon.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
@ -25,7 +26,6 @@ import 'package:path/path.dart' as p;
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:settings/settings.dart'; import 'package:settings/settings.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'dialogs/choose_create.dart'; part 'dialogs/choose_create.dart';
part 'dialogs/choose_folder.dart'; part 'dialogs/choose_folder.dart';
@ -40,23 +40,30 @@ part 'widgets/browser_view.dart';
part 'widgets/file_preview.dart'; part 'widgets/file_preview.dart';
class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> { class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> {
FilesApp( FilesApp(super.sharedPreferences, super.requestManager, super.platform);
final SharedPreferences sharedPreferences,
final RequestManager requestManager, @override
final NeonPlatform platform, String id = 'files';
) : super(
'files', @override
(final localizations) => localizations.filesName, String nameFromLocalization(AppLocalizations localizations) => localizations.filesName;
sharedPreferences,
FilesAppSpecificOptions.new, @override
(final options, final client) => FilesBloc( FilesAppSpecificOptions buildOptions(Storage storage) => FilesAppSpecificOptions(storage);
@override
FilesBloc buildBloc(NextcloudClient client) => FilesBloc(
options, options,
requestManager, requestManager,
client, client,
platform, platform,
),
(final context, final bloc) => FilesMainPage(
bloc: bloc,
),
); );
@override
Widget buildPage(BuildContext context, AppsBloc appsBloc) => FilesMainPage(
bloc: appsBloc.getAppBloc(this),
);
@override
BehaviorSubject<int>? getUnreadCounter(AppsBloc appsBloc) => null;
} }

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

@ -14,6 +14,7 @@ import 'package:neon/l10n/localizations.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/accounts.dart';
import 'package:neon/src/blocs/apps.dart';
import 'package:neon/src/models/account.dart'; import 'package:neon/src/models/account.dart';
import 'package:neon/src/neon.dart'; import 'package:neon/src/neon.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
@ -21,7 +22,6 @@ import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:settings/settings.dart'; import 'package:settings/settings.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sort_box/sort_box.dart'; import 'package:sort_box/sort_box.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
@ -48,22 +48,29 @@ part 'widgets/folder_view.dart';
part 'widgets/folders_view.dart'; part 'widgets/folders_view.dart';
class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> { class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> {
NewsApp( NewsApp(super.sharedPreferences, super.requestManager, super.platform);
final SharedPreferences sharedPreferences,
final RequestManager requestManager, @override
final NeonPlatform platform, String id = 'news';
) : super(
'news', @override
(final localizations) => localizations.newsName, String nameFromLocalization(AppLocalizations localizations) => localizations.newsName;
sharedPreferences,
(final storage) => NewsAppSpecificOptions(storage, platform), @override
(final options, final client) => NewsBloc( NewsAppSpecificOptions buildOptions(Storage storage) => NewsAppSpecificOptions(storage, platform);
@override
NewsBloc buildBloc(NextcloudClient client) => NewsBloc(
options, options,
requestManager, requestManager,
client, client,
),
(final context, final bloc) => NewsMainPage(
bloc: bloc,
),
); );
@override
Widget buildPage(BuildContext context, AppsBloc appsBloc) => NewsMainPage(
bloc: appsBloc.getAppBloc(this),
);
@override
BehaviorSubject<int>? getUnreadCounter(AppsBloc appsBloc) => appsBloc.getAppBloc<NewsBloc>(this).unreadCounter;
} }

15
packages/neon/lib/src/apps/news/blocs/news.dart

@ -38,6 +38,8 @@ abstract class NewsBlocStates {
BehaviorSubject<Result<List<NewsFeed>>> get feeds; BehaviorSubject<Result<List<NewsFeed>>> get feeds;
Stream<Exception> get errors; Stream<Exception> get errors;
BehaviorSubject<int> get unreadCounter;
} }
@RxBloc() @RxBloc()
@ -146,6 +148,14 @@ class NewsBloc extends $NewsBloc {
}); });
}); });
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); _loadAll(false);
} }
@ -223,12 +233,14 @@ class NewsBloc extends $NewsBloc {
final _foldersSubject = BehaviorSubject<Result<List<NewsFolder>>>(); final _foldersSubject = BehaviorSubject<Result<List<NewsFolder>>>();
final _feedsSubject = BehaviorSubject<Result<List<NewsFeed>>>(); final _feedsSubject = BehaviorSubject<Result<List<NewsFeed>>>();
final _errorsStreamController = StreamController<Exception>(); final _errorsStreamController = StreamController<Exception>();
final _unreadCounterSubject = BehaviorSubject<int>();
@override @override
void dispose() { void dispose() {
_foldersSubject.close(); _foldersSubject.close();
_feedsSubject.close(); _feedsSubject.close();
_errorsStreamController.close(); _errorsStreamController.close();
_unreadCounterSubject.close();
super.dispose(); super.dispose();
} }
@ -240,4 +252,7 @@ class NewsBloc extends $NewsBloc {
@override @override
Stream<Exception> _mapToErrorsState() => _errorsStreamController.stream.asBroadcastStream(); Stream<Exception> _mapToErrorsState() => _errorsStreamController.stream.asBroadcastStream();
@override
BehaviorSubject<int> _mapToUnreadCounterState() => _unreadCounterSubject;
} }

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

@ -57,6 +57,9 @@ abstract class $NewsBloc extends RxBlocBase implements NewsBlocEvents, NewsBlocS
/// 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();
/// The state of [unreadCounter] implemented in [_mapToUnreadCounterState]
late final BehaviorSubject<int> _unreadCounterState = _mapToUnreadCounterState();
@override @override
void refresh({required bool mainArticlesToo}) => _$refreshEvent.add(mainArticlesToo); void refresh({required bool mainArticlesToo}) => _$refreshEvent.add(mainArticlesToo);
@ -96,12 +99,17 @@ abstract class $NewsBloc extends RxBlocBase implements NewsBlocEvents, NewsBlocS
@override @override
Stream<Exception> get errors => _errorsState; Stream<Exception> get errors => _errorsState;
@override
BehaviorSubject<int> get unreadCounter => _unreadCounterState;
BehaviorSubject<Result<List<NewsFolder>>> _mapToFoldersState(); BehaviorSubject<Result<List<NewsFolder>>> _mapToFoldersState();
BehaviorSubject<Result<List<NewsFeed>>> _mapToFeedsState(); BehaviorSubject<Result<List<NewsFeed>>> _mapToFeedsState();
Stream<Exception> _mapToErrorsState(); Stream<Exception> _mapToErrorsState();
BehaviorSubject<int> _mapToUnreadCounterState();
@override @override
NewsBlocEvents get events => this; NewsBlocEvents get events => this;

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

@ -9,13 +9,13 @@ import 'package:intersperse/intersperse.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/notes.dart'; import 'package:neon/src/apps/notes/blocs/notes.dart';
import 'package:neon/src/blocs/apps.dart';
import 'package:neon/src/neon.dart'; import 'package:neon/src/neon.dart';
import 'package:neon/src/widgets/custom_auto_complete.dart'; import 'package:neon/src/widgets/custom_auto_complete.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:settings/settings.dart'; import 'package:settings/settings.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sort_box/sort_box.dart'; import 'package:sort_box/sort_box.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:wakelock/wakelock.dart'; import 'package:wakelock/wakelock.dart';
@ -35,21 +35,29 @@ part 'widgets/category_select.dart';
part 'widgets/notes_view.dart'; part 'widgets/notes_view.dart';
class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> { class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> {
NotesApp( NotesApp(super.sharedPreferences, super.requestManager, super.platform);
final SharedPreferences sharedPreferences,
final RequestManager requestManager, @override
) : super( String id = 'notes';
'notes',
(final localizations) => localizations.notesName, @override
sharedPreferences, String nameFromLocalization(AppLocalizations localizations) => localizations.notesName;
NotesAppSpecificOptions.new,
(final options, final client) => NotesBloc( @override
NotesAppSpecificOptions buildOptions(Storage storage) => NotesAppSpecificOptions(storage);
@override
NotesBloc buildBloc(NextcloudClient client) => NotesBloc(
options, options,
requestManager, requestManager,
client, client,
),
(final context, final bloc) => NotesMainPage(
bloc: bloc,
),
); );
@override
Widget buildPage(BuildContext context, AppsBloc appsBloc) => NotesMainPage(
bloc: appsBloc.getAppBloc(this),
);
@override
BehaviorSubject<int>? getUnreadCounter(AppsBloc appsBloc) => null;
} }

38
packages/neon/lib/src/apps/notifications/app.dart

@ -5,30 +5,40 @@ import 'package:intersperse/intersperse.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/notifications/blocs/notifications.dart'; import 'package:neon/src/apps/notifications/blocs/notifications.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';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:rxdart/rxdart.dart';
part 'options.dart'; part 'options.dart';
part 'pages/main.dart'; part 'pages/main.dart';
class NotificationsApp extends AppImplementation<NotificationsBloc, NotificationsAppSpecificOptions> { class NotificationsApp extends AppImplementation<NotificationsBloc, NotificationsAppSpecificOptions> {
NotificationsApp( NotificationsApp(super.sharedPreferences, super.requestManager, super.platform);
final SharedPreferences sharedPreferences,
final RequestManager requestManager, @override
) : super( String id = 'notifications';
'notifications',
(final localizations) => localizations.notificationsName, @override
sharedPreferences, String nameFromLocalization(AppLocalizations localizations) => localizations.notificationsName;
NotificationsAppSpecificOptions.new,
(final options, final client) => NotificationsBloc( @override
NotificationsAppSpecificOptions buildOptions(Storage storage) => NotificationsAppSpecificOptions(storage);
@override
NotificationsBloc buildBloc(NextcloudClient client) => NotificationsBloc(
options, options,
requestManager, requestManager,
client, client,
),
(final context, final bloc) => NotificationsMainPage(
bloc: bloc,
),
); );
@override
Widget buildPage(BuildContext context, AppsBloc appsBloc) => NotificationsMainPage(
bloc: appsBloc.getAppBloc(this),
);
@override
BehaviorSubject<int>? getUnreadCounter(AppsBloc appsBloc) =>
appsBloc.getAppBloc<NotificationsBloc>(this).unreadCounter;
} }

14
packages/neon/lib/src/apps/notifications/blocs/notifications.dart

@ -21,6 +21,8 @@ abstract class NotificationsBlocStates {
BehaviorSubject<Result<List<NotificationsNotification>>> get notifications; BehaviorSubject<Result<List<NotificationsNotification>>> get notifications;
Stream<Exception> get errors; Stream<Exception> get errors;
BehaviorSubject<int> get unreadCounter;
} }
@RxBloc() @RxBloc()
@ -35,10 +37,17 @@ class NotificationsBloc extends $NotificationsBloc {
_$deleteNotificationEvent.listen((final notification) { _$deleteNotificationEvent.listen((final notification) {
_wrapAction(() async => client.notifications.deleteNotification(notification.notificationId!)); _wrapAction(() async => client.notifications.deleteNotification(notification.notificationId!));
}); });
_$deleteAllNotificationsEvent.listen((final notification) { _$deleteAllNotificationsEvent.listen((final notification) {
_wrapAction(() async => client.notifications.deleteAllNotifications()); _wrapAction(() async => client.notifications.deleteAllNotifications());
}); });
_notificationsSubject.listen((final result) {
if (result.data != null) {
_unreadCounterSubject.add(result.data!.length);
}
});
_loadNotifications(); _loadNotifications();
} }
@ -70,11 +79,13 @@ class NotificationsBloc extends $NotificationsBloc {
final _notificationsSubject = BehaviorSubject<Result<List<NotificationsNotification>>>(); final _notificationsSubject = BehaviorSubject<Result<List<NotificationsNotification>>>();
final _errorsStreamController = StreamController<Exception>(); final _errorsStreamController = StreamController<Exception>();
final _unreadCounterSubject = BehaviorSubject<int>();
@override @override
void dispose() { void dispose() {
_notificationsSubject.close(); _notificationsSubject.close();
_errorsStreamController.close(); _errorsStreamController.close();
_unreadCounterSubject.close();
super.dispose(); super.dispose();
} }
@ -83,4 +94,7 @@ class NotificationsBloc extends $NotificationsBloc {
@override @override
Stream<Exception> _mapToErrorsState() => _errorsStreamController.stream.asBroadcastStream(); Stream<Exception> _mapToErrorsState() => _errorsStreamController.stream.asBroadcastStream();
@override
BehaviorSubject<int> _mapToUnreadCounterState() => _unreadCounterSubject;
} }

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

@ -34,6 +34,9 @@ abstract class $NotificationsBloc extends RxBlocBase
/// 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();
/// The state of [unreadCounter] implemented in [_mapToUnreadCounterState]
late final BehaviorSubject<int> _unreadCounterState = _mapToUnreadCounterState();
@override @override
void refresh() => _$refreshEvent.add(null); void refresh() => _$refreshEvent.add(null);
@ -49,10 +52,15 @@ abstract class $NotificationsBloc extends RxBlocBase
@override @override
Stream<Exception> get errors => _errorsState; Stream<Exception> get errors => _errorsState;
@override
BehaviorSubject<int> get unreadCounter => _unreadCounterState;
BehaviorSubject<Result<List<NotificationsNotification>>> _mapToNotificationsState(); BehaviorSubject<Result<List<NotificationsNotification>>> _mapToNotificationsState();
Stream<Exception> _mapToErrorsState(); Stream<Exception> _mapToErrorsState();
BehaviorSubject<int> _mapToUnreadCounterState();
@override @override
NotificationsBlocEvents get events => this; NotificationsBlocEvents get events => this;

21
packages/neon/lib/src/pages/home/home.dart

@ -508,7 +508,26 @@ class _HomePageState extends State<HomePage> with tray.TrayListener, WindowListe
if (appsData.map((final a) => a.id).contains(appImplementation.id)) ...[ if (appsData.map((final a) => a.id).contains(appImplementation.id)) ...[
ListTile( ListTile(
key: Key('app-${appImplementation.id}'), key: Key('app-${appImplementation.id}'),
title: Text(appImplementation.name(context)), title: StreamBuilder<int>(
stream: appImplementation.getUnreadCounter(_appsBloc) ??
BehaviorSubject<int>.seeded(0),
builder: (final context, final unreadCounterSnapshot) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(appImplementation.name(context)),
if (unreadCounterSnapshot.hasData && unreadCounterSnapshot.data! > 0) ...[
Text(
unreadCounterSnapshot.data!.toString(),
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
],
],
),
),
leading: appImplementation.buildIcon(context), leading: appImplementation.buildIcon(context),
minLeadingWidth: 0, minLeadingWidth: 0,
onTap: () { onTap: () {

29
packages/neon/lib/src/utils/app_implementation.dart

@ -8,34 +8,35 @@ List<AppImplementation> getAppImplementations(
[ [
FilesApp(sharedPreferences, requestManager, platform), FilesApp(sharedPreferences, requestManager, platform),
NewsApp(sharedPreferences, requestManager, platform), NewsApp(sharedPreferences, requestManager, platform),
NotesApp(sharedPreferences, requestManager), NotesApp(sharedPreferences, requestManager, platform),
NotificationsApp(sharedPreferences, requestManager), NotificationsApp(sharedPreferences, requestManager, platform),
]; ];
abstract class AppImplementation<T extends RxBlocBase, R extends NextcloudAppSpecificOptions> { abstract class AppImplementation<T extends RxBlocBase, R extends NextcloudAppSpecificOptions> {
AppImplementation( AppImplementation(
this.id,
this.nameFromLocalization,
final SharedPreferences sharedPreferences, final SharedPreferences sharedPreferences,
final R Function(Storage) buildOptions, final this.requestManager,
this._buildBloc, final this.platform,
this._buildPage,
) { ) {
final storage = Storage('app-$id', sharedPreferences); final storage = Storage('app-$id', sharedPreferences);
options = buildOptions(storage); options = buildOptions(storage);
} }
final String Function(AppLocalizations) nameFromLocalization; String get id;
final RequestManager requestManager;
final NeonPlatform platform;
String nameFromLocalization(AppLocalizations localizations);
String name(BuildContext context) => nameFromLocalization(AppLocalizations.of(context)); String name(BuildContext context) => nameFromLocalization(AppLocalizations.of(context));
final String id;
late final R options; late final R options;
final T Function(R options, NextcloudClient client) _buildBloc; R buildOptions(Storage storage);
final Widget Function(BuildContext context, T bloc) _buildPage;
T buildBloc(final NextcloudClient client);
T buildBloc(final NextcloudClient client) => _buildBloc(options, client); BehaviorSubject<int>? getUnreadCounter(AppsBloc appsBloc);
Widget buildPage(final BuildContext context, final AppsBloc appsBloc) => Widget buildPage(BuildContext context, AppsBloc appsBloc);
_buildPage(context, appsBloc.getAppBloc(this));
Widget buildIcon( Widget buildIcon(
final BuildContext context, { final BuildContext context, {

Loading…
Cancel
Save