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. 47
      packages/neon/lib/src/apps/files/app.dart
  2. 45
      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. 44
      packages/neon/lib/src/apps/notes/app.dart
  6. 46
      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

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

45
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/news.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/neon.dart';
import 'package:nextcloud/nextcloud.dart';
@ -21,7 +22,6 @@ import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart';
import 'package:settings/settings.dart';
import 'package:share_plus/share_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sort_box/sort_box.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:wakelock/wakelock.dart';
@ -48,22 +48,29 @@ part 'widgets/folder_view.dart';
part 'widgets/folders_view.dart';
class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> {
NewsApp(
final SharedPreferences sharedPreferences,
final RequestManager requestManager,
final NeonPlatform platform,
) : super(
'news',
(final localizations) => localizations.newsName,
sharedPreferences,
(final storage) => NewsAppSpecificOptions(storage, platform),
(final options, final client) => NewsBloc(
options,
requestManager,
client,
),
(final context, final bloc) => NewsMainPage(
bloc: bloc,
),
);
NewsApp(super.sharedPreferences, super.requestManager, super.platform);
@override
String id = 'news';
@override
String nameFromLocalization(AppLocalizations localizations) => localizations.newsName;
@override
NewsAppSpecificOptions buildOptions(Storage storage) => NewsAppSpecificOptions(storage, platform);
@override
NewsBloc buildBloc(NextcloudClient client) => NewsBloc(
options,
requestManager,
client,
);
@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;
Stream<Exception> get errors;
BehaviorSubject<int> get unreadCounter;
}
@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);
}
@ -223,12 +233,14 @@ class NewsBloc extends $NewsBloc {
final _foldersSubject = BehaviorSubject<Result<List<NewsFolder>>>();
final _feedsSubject = BehaviorSubject<Result<List<NewsFeed>>>();
final _errorsStreamController = StreamController<Exception>();
final _unreadCounterSubject = BehaviorSubject<int>();
@override
void dispose() {
_foldersSubject.close();
_feedsSubject.close();
_errorsStreamController.close();
_unreadCounterSubject.close();
super.dispose();
}
@ -240,4 +252,7 @@ class NewsBloc extends $NewsBloc {
@override
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]
late final Stream<Exception> _errorsState = _mapToErrorsState();
/// The state of [unreadCounter] implemented in [_mapToUnreadCounterState]
late final BehaviorSubject<int> _unreadCounterState = _mapToUnreadCounterState();
@override
void refresh({required bool mainArticlesToo}) => _$refreshEvent.add(mainArticlesToo);
@ -96,12 +99,17 @@ abstract class $NewsBloc extends RxBlocBase implements NewsBlocEvents, NewsBlocS
@override
Stream<Exception> get errors => _errorsState;
@override
BehaviorSubject<int> get unreadCounter => _unreadCounterState;
BehaviorSubject<Result<List<NewsFolder>>> _mapToFoldersState();
BehaviorSubject<Result<List<NewsFeed>>> _mapToFeedsState();
Stream<Exception> _mapToErrorsState();
BehaviorSubject<int> _mapToUnreadCounterState();
@override
NewsBlocEvents get events => this;

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

46
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:neon/l10n/localizations.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:nextcloud/nextcloud.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:rxdart/rxdart.dart';
part 'options.dart';
part 'pages/main.dart';
class NotificationsApp extends AppImplementation<NotificationsBloc, NotificationsAppSpecificOptions> {
NotificationsApp(
final SharedPreferences sharedPreferences,
final RequestManager requestManager,
) : super(
'notifications',
(final localizations) => localizations.notificationsName,
sharedPreferences,
NotificationsAppSpecificOptions.new,
(final options, final client) => NotificationsBloc(
options,
requestManager,
client,
),
(final context, final bloc) => NotificationsMainPage(
bloc: bloc,
),
);
NotificationsApp(super.sharedPreferences, super.requestManager, super.platform);
@override
String id = 'notifications';
@override
String nameFromLocalization(AppLocalizations localizations) => localizations.notificationsName;
@override
NotificationsAppSpecificOptions buildOptions(Storage storage) => NotificationsAppSpecificOptions(storage);
@override
NotificationsBloc buildBloc(NextcloudClient client) => NotificationsBloc(
options,
requestManager,
client,
);
@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;
Stream<Exception> get errors;
BehaviorSubject<int> get unreadCounter;
}
@RxBloc()
@ -35,10 +37,17 @@ class NotificationsBloc extends $NotificationsBloc {
_$deleteNotificationEvent.listen((final notification) {
_wrapAction(() async => client.notifications.deleteNotification(notification.notificationId!));
});
_$deleteAllNotificationsEvent.listen((final notification) {
_wrapAction(() async => client.notifications.deleteAllNotifications());
});
_notificationsSubject.listen((final result) {
if (result.data != null) {
_unreadCounterSubject.add(result.data!.length);
}
});
_loadNotifications();
}
@ -70,11 +79,13 @@ class NotificationsBloc extends $NotificationsBloc {
final _notificationsSubject = BehaviorSubject<Result<List<NotificationsNotification>>>();
final _errorsStreamController = StreamController<Exception>();
final _unreadCounterSubject = BehaviorSubject<int>();
@override
void dispose() {
_notificationsSubject.close();
_errorsStreamController.close();
_unreadCounterSubject.close();
super.dispose();
}
@ -83,4 +94,7 @@ class NotificationsBloc extends $NotificationsBloc {
@override
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]
late final Stream<Exception> _errorsState = _mapToErrorsState();
/// The state of [unreadCounter] implemented in [_mapToUnreadCounterState]
late final BehaviorSubject<int> _unreadCounterState = _mapToUnreadCounterState();
@override
void refresh() => _$refreshEvent.add(null);
@ -49,10 +52,15 @@ abstract class $NotificationsBloc extends RxBlocBase
@override
Stream<Exception> get errors => _errorsState;
@override
BehaviorSubject<int> get unreadCounter => _unreadCounterState;
BehaviorSubject<Result<List<NotificationsNotification>>> _mapToNotificationsState();
Stream<Exception> _mapToErrorsState();
BehaviorSubject<int> _mapToUnreadCounterState();
@override
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)) ...[
ListTile(
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),
minLeadingWidth: 0,
onTap: () {

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

@ -8,34 +8,35 @@ List<AppImplementation> getAppImplementations(
[
FilesApp(sharedPreferences, requestManager, platform),
NewsApp(sharedPreferences, requestManager, platform),
NotesApp(sharedPreferences, requestManager),
NotificationsApp(sharedPreferences, requestManager),
NotesApp(sharedPreferences, requestManager, platform),
NotificationsApp(sharedPreferences, requestManager, platform),
];
abstract class AppImplementation<T extends RxBlocBase, R extends NextcloudAppSpecificOptions> {
AppImplementation(
this.id,
this.nameFromLocalization,
final SharedPreferences sharedPreferences,
final R Function(Storage) buildOptions,
this._buildBloc,
this._buildPage,
final this.requestManager,
final this.platform,
) {
final storage = Storage('app-$id', sharedPreferences);
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));
final String id;
late final R options;
final T Function(R options, NextcloudClient client) _buildBloc;
final Widget Function(BuildContext context, T bloc) _buildPage;
R buildOptions(Storage storage);
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) =>
_buildPage(context, appsBloc.getAppBloc(this));
Widget buildPage(BuildContext context, AppsBloc appsBloc);
Widget buildIcon(
final BuildContext context, {

Loading…
Cancel
Save