Initial notifications refactoring with DMs notifications as proof of concept

merge-requests/70/head
Hank Grabowski 2025-06-10 17:31:28 -04:00
rodzic c8967ed75a
commit 7bf1fe98bf
3 zmienionych plików z 609 dodań i 34 usunięć

Wyświetl plik

@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
@ -26,6 +27,63 @@ const _itemsPerQuery = 50;
const _minimumDmsAndCrsUpdateDuration = Duration(seconds: 30);
final _logger = Logger('NotificationManager');
@Riverpod(keepAlive: true)
class NotificationsManager2 extends _$NotificationsManager2 {
final _values = <UserNotification>{};
@override
List<UserNotification> build(Profile profile, NotificationType type) {
return _rval;
}
void upsert(UserNotification notification) {
_values.remove(notification);
_values.add(notification);
state = _rval;
}
void clear() {
_values.clear();
state = _rval;
}
void markAllRead() {
final updated = _values.map((n) => n.copy(dismissed: true));
_values.clear();
_values.addAll(updated);
}
List<UserNotification> get _rval => _values.toList()..sort();
@override
bool updateShouldNotify(
List<UserNotification> previous, List<UserNotification> next) {
return !listEquals(previous, next);
}
}
@riverpod
List<UserNotification> userNotifications(
Ref ref, Profile profile, NotificationType type, bool isRead) {
_logger.info('Build UserNotificationsProvider($type,$isRead,$profile)');
final notifications = ref.watch(NotificationsManager2Provider(profile, type));
return notifications.where((n) => n.dismissed == isRead).toList();
}
@riverpod
bool hasNotifications(Ref ref, Profile profile) {
_logger.info('Build hasNotifications($profile)');
var hasUnread = false;
// Go through all to watch all for changes
for (NotificationType type in NotificationType.values) {
hasUnread |=
ref.watch(userNotificationsProvider(profile, type, false)).isNotEmpty;
}
return hasUnread;
}
@Riverpod(keepAlive: true)
class NotificationsManager extends _$NotificationsManager {
final dms = <UserNotification>[];
@ -36,10 +94,18 @@ class NotificationsManager extends _$NotificationsManager {
var lastCrUpdate = DateTime(1900);
bool get hasNotifications =>
dms.isNotEmpty || connectionRequests.isNotEmpty || unread.isNotEmpty;
ref.read(hasNotificationsProvider(profile)) ||
// connectionRequests.isNotEmpty ||
unread.isNotEmpty;
List<UserNotification> get notifications =>
[...connectionRequests, ...dms, ...unread, ...read];
List<UserNotification> get notifications => [
...ref.read(userNotificationsProvider(
profile, NotificationType.follow_request, false)),
...ref.read(userNotificationsProvider(
profile, NotificationType.direct_message, false)),
...unread,
...read
];
@override
Future<Result<List<UserNotification>, ExecError>> build(
@ -47,7 +113,7 @@ class NotificationsManager extends _$NotificationsManager {
_logger.info('Building');
await _initialize();
return Result.ok(notifications);
return Result.ok([]);
}
Future<void> _initialize() async {
@ -63,7 +129,11 @@ class NotificationsManager extends _$NotificationsManager {
Future<void> clearConnectionRequestNotifications() async {
_logger.info('clearConnectionRequestNotifications');
connectionRequests.clear();
ref
.read(notificationsManager2Provider(
profile, NotificationType.follow_request)
.notifier)
.clear();
state = AsyncData(Result.ok(notifications));
}
@ -80,7 +150,11 @@ class NotificationsManager extends _$NotificationsManager {
Future<void> refreshDms() async {
_logger.info('refreshDms');
dms.clear();
ref
.read(notificationsManager2Provider(
profile, NotificationType.direct_message)
.notifier)
.clear();
await _postFetchOperations(
[],
true,
@ -90,8 +164,16 @@ class NotificationsManager extends _$NotificationsManager {
}
void clear({bool withListenerNotification = true}) {
dms.clear();
connectionRequests.clear();
ref
.read(notificationsManager2Provider(
profile, NotificationType.direct_message)
.notifier)
.clear();
ref
.read(notificationsManager2Provider(
profile, NotificationType.follow_request)
.notifier)
.clear();
unread.clear();
read.clear();
_initialize();
@ -318,10 +400,6 @@ class NotificationsManager extends _$NotificationsManager {
final st = Stopwatch()..start();
for (int i = 0; i < dms.length; i++) {
dmsMap[dms[i].id] = dms[i];
}
if (st.elapsedMilliseconds > maxProcessingMillis) {
await Future.delayed(processingSleep, () => st.reset());
}
@ -346,7 +424,6 @@ class NotificationsManager extends _$NotificationsManager {
readMap[read[i].id] = read[i];
}
dms.clear();
connectionRequests.clear();
unread.clear();
read.clear();
@ -366,7 +443,9 @@ class NotificationsManager extends _$NotificationsManager {
switch (n.type) {
case NotificationType.direct_message:
dmsMap[n.id] = n;
ref
.read(notificationsManager2Provider(profile, n.type).notifier)
.upsert(n);
break;
case NotificationType.follow_request:
crMap[n.id] = n;
@ -376,9 +455,6 @@ class NotificationsManager extends _$NotificationsManager {
}
}
dms
..addAll(dmsMap.values)
..sort();
connectionRequests
..addAll(crMap.values)
..sort();

Wyświetl plik

@ -6,8 +6,7 @@ part of 'notification_services.dart';
// RiverpodGenerator
// **************************************************************************
String _$notificationsManagerHash() =>
r'c92bf43255b0c2b6b4a94c4239e545ee1c9bf90f';
String _$userNotificationsHash() => r'1ee8dea8ca12c3cdec3c274103422bdd89ba9922';
/// Copied from Dart SDK
class _SystemHash {
@ -30,6 +29,472 @@ class _SystemHash {
}
}
/// See also [userNotifications].
@ProviderFor(userNotifications)
const userNotificationsProvider = UserNotificationsFamily();
/// See also [userNotifications].
class UserNotificationsFamily extends Family<List<UserNotification>> {
/// See also [userNotifications].
const UserNotificationsFamily();
/// See also [userNotifications].
UserNotificationsProvider call(
Profile profile,
NotificationType type,
bool isRead,
) {
return UserNotificationsProvider(
profile,
type,
isRead,
);
}
@override
UserNotificationsProvider getProviderOverride(
covariant UserNotificationsProvider provider,
) {
return call(
provider.profile,
provider.type,
provider.isRead,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'userNotificationsProvider';
}
/// See also [userNotifications].
class UserNotificationsProvider
extends AutoDisposeProvider<List<UserNotification>> {
/// See also [userNotifications].
UserNotificationsProvider(
Profile profile,
NotificationType type,
bool isRead,
) : this._internal(
(ref) => userNotifications(
ref as UserNotificationsRef,
profile,
type,
isRead,
),
from: userNotificationsProvider,
name: r'userNotificationsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$userNotificationsHash,
dependencies: UserNotificationsFamily._dependencies,
allTransitiveDependencies:
UserNotificationsFamily._allTransitiveDependencies,
profile: profile,
type: type,
isRead: isRead,
);
UserNotificationsProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.profile,
required this.type,
required this.isRead,
}) : super.internal();
final Profile profile;
final NotificationType type;
final bool isRead;
@override
Override overrideWith(
List<UserNotification> Function(UserNotificationsRef provider) create,
) {
return ProviderOverride(
origin: this,
override: UserNotificationsProvider._internal(
(ref) => create(ref as UserNotificationsRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
profile: profile,
type: type,
isRead: isRead,
),
);
}
@override
AutoDisposeProviderElement<List<UserNotification>> createElement() {
return _UserNotificationsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is UserNotificationsProvider &&
other.profile == profile &&
other.type == type &&
other.isRead == isRead;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, profile.hashCode);
hash = _SystemHash.combine(hash, type.hashCode);
hash = _SystemHash.combine(hash, isRead.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin UserNotificationsRef on AutoDisposeProviderRef<List<UserNotification>> {
/// The parameter `profile` of this provider.
Profile get profile;
/// The parameter `type` of this provider.
NotificationType get type;
/// The parameter `isRead` of this provider.
bool get isRead;
}
class _UserNotificationsProviderElement
extends AutoDisposeProviderElement<List<UserNotification>>
with UserNotificationsRef {
_UserNotificationsProviderElement(super.provider);
@override
Profile get profile => (origin as UserNotificationsProvider).profile;
@override
NotificationType get type => (origin as UserNotificationsProvider).type;
@override
bool get isRead => (origin as UserNotificationsProvider).isRead;
}
String _$hasNotificationsHash() => r'19b30cde983188d03c0a925cba4cd9a7aa2895be';
/// See also [hasNotifications].
@ProviderFor(hasNotifications)
const hasNotificationsProvider = HasNotificationsFamily();
/// See also [hasNotifications].
class HasNotificationsFamily extends Family<bool> {
/// See also [hasNotifications].
const HasNotificationsFamily();
/// See also [hasNotifications].
HasNotificationsProvider call(
Profile profile,
) {
return HasNotificationsProvider(
profile,
);
}
@override
HasNotificationsProvider getProviderOverride(
covariant HasNotificationsProvider provider,
) {
return call(
provider.profile,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'hasNotificationsProvider';
}
/// See also [hasNotifications].
class HasNotificationsProvider extends AutoDisposeProvider<bool> {
/// See also [hasNotifications].
HasNotificationsProvider(
Profile profile,
) : this._internal(
(ref) => hasNotifications(
ref as HasNotificationsRef,
profile,
),
from: hasNotificationsProvider,
name: r'hasNotificationsProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$hasNotificationsHash,
dependencies: HasNotificationsFamily._dependencies,
allTransitiveDependencies:
HasNotificationsFamily._allTransitiveDependencies,
profile: profile,
);
HasNotificationsProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.profile,
}) : super.internal();
final Profile profile;
@override
Override overrideWith(
bool Function(HasNotificationsRef provider) create,
) {
return ProviderOverride(
origin: this,
override: HasNotificationsProvider._internal(
(ref) => create(ref as HasNotificationsRef),
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
profile: profile,
),
);
}
@override
AutoDisposeProviderElement<bool> createElement() {
return _HasNotificationsProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is HasNotificationsProvider && other.profile == profile;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, profile.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin HasNotificationsRef on AutoDisposeProviderRef<bool> {
/// The parameter `profile` of this provider.
Profile get profile;
}
class _HasNotificationsProviderElement extends AutoDisposeProviderElement<bool>
with HasNotificationsRef {
_HasNotificationsProviderElement(super.provider);
@override
Profile get profile => (origin as HasNotificationsProvider).profile;
}
String _$notificationsManager2Hash() =>
r'107d2e6a65815060148ee000d74bceea39cb76a9';
abstract class _$NotificationsManager2
extends BuildlessNotifier<List<UserNotification>> {
late final Profile profile;
late final NotificationType type;
List<UserNotification> build(
Profile profile,
NotificationType type,
);
}
/// See also [NotificationsManager2].
@ProviderFor(NotificationsManager2)
const notificationsManager2Provider = NotificationsManager2Family();
/// See also [NotificationsManager2].
class NotificationsManager2Family extends Family<List<UserNotification>> {
/// See also [NotificationsManager2].
const NotificationsManager2Family();
/// See also [NotificationsManager2].
NotificationsManager2Provider call(
Profile profile,
NotificationType type,
) {
return NotificationsManager2Provider(
profile,
type,
);
}
@override
NotificationsManager2Provider getProviderOverride(
covariant NotificationsManager2Provider provider,
) {
return call(
provider.profile,
provider.type,
);
}
static const Iterable<ProviderOrFamily>? _dependencies = null;
@override
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
@override
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
_allTransitiveDependencies;
@override
String? get name => r'notificationsManager2Provider';
}
/// See also [NotificationsManager2].
class NotificationsManager2Provider extends NotifierProviderImpl<
NotificationsManager2, List<UserNotification>> {
/// See also [NotificationsManager2].
NotificationsManager2Provider(
Profile profile,
NotificationType type,
) : this._internal(
() => NotificationsManager2()
..profile = profile
..type = type,
from: notificationsManager2Provider,
name: r'notificationsManager2Provider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product')
? null
: _$notificationsManager2Hash,
dependencies: NotificationsManager2Family._dependencies,
allTransitiveDependencies:
NotificationsManager2Family._allTransitiveDependencies,
profile: profile,
type: type,
);
NotificationsManager2Provider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
required super.allTransitiveDependencies,
required super.debugGetCreateSourceHash,
required super.from,
required this.profile,
required this.type,
}) : super.internal();
final Profile profile;
final NotificationType type;
@override
List<UserNotification> runNotifierBuild(
covariant NotificationsManager2 notifier,
) {
return notifier.build(
profile,
type,
);
}
@override
Override overrideWith(NotificationsManager2 Function() create) {
return ProviderOverride(
origin: this,
override: NotificationsManager2Provider._internal(
() => create()
..profile = profile
..type = type,
from: from,
name: null,
dependencies: null,
allTransitiveDependencies: null,
debugGetCreateSourceHash: null,
profile: profile,
type: type,
),
);
}
@override
NotifierProviderElement<NotificationsManager2, List<UserNotification>>
createElement() {
return _NotificationsManager2ProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is NotificationsManager2Provider &&
other.profile == profile &&
other.type == type;
}
@override
int get hashCode {
var hash = _SystemHash.combine(0, runtimeType.hashCode);
hash = _SystemHash.combine(hash, profile.hashCode);
hash = _SystemHash.combine(hash, type.hashCode);
return _SystemHash.finish(hash);
}
}
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin NotificationsManager2Ref on NotifierProviderRef<List<UserNotification>> {
/// The parameter `profile` of this provider.
Profile get profile;
/// The parameter `type` of this provider.
NotificationType get type;
}
class _NotificationsManager2ProviderElement extends NotifierProviderElement<
NotificationsManager2,
List<UserNotification>> with NotificationsManager2Ref {
_NotificationsManager2ProviderElement(super.provider);
@override
Profile get profile => (origin as NotificationsManager2Provider).profile;
@override
NotificationType get type => (origin as NotificationsManager2Provider).type;
}
String _$notificationsManagerHash() =>
r'030cc42fb7b5f7edaaa8eeacf154dfc9887e39b6';
abstract class _$NotificationsManager
extends BuildlessAsyncNotifier<Result<List<UserNotification>, ExecError>> {
late final Profile profile;

Wyświetl plik

@ -13,6 +13,7 @@ import '../controls/standard_appbar.dart';
import '../controls/status_and_refresh_button.dart';
import '../globals.dart';
import '../models/auth/profile.dart';
import '../models/user_notification.dart';
import '../riverpod_controllers/account_services.dart';
import '../riverpod_controllers/networking/network_status_services.dart';
import '../riverpod_controllers/notification_services.dart';
@ -93,21 +94,25 @@ class NotificationsScreen extends ConsumerWidget {
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
if (index == 0) {
return TextButton(
onPressed: () async {
final result = await ref
.read(notificationsManagerProvider(profile)
.notifier)
.loadNewerNotifications();
final noMore = result.fold(
onSuccess: (value) => !value,
onError: (_) => true);
if (context.mounted && noMore) {
buildSnackbar(
context, 'No newer notifications to load');
}
},
child: const Text('Load newer notifications'));
return _NotificationsListElement(
profile: profile,
type: NotificationType.direct_message,
isRead: false);
// return TextButton(
// onPressed: () async {
// final result = await ref
// .read(notificationsManagerProvider(profile)
// .notifier)
// .loadNewerNotifications();
// final noMore = result.fold(
// onSuccess: (value) => !value,
// onError: (_) => true);
// if (context.mounted && noMore) {
// buildSnackbar(
// context, 'No newer notifications to load');
// }
// },
// child: const Text('Load newer notifications'));
}
if (index == notifications.length + 1) {
return TextButton(
@ -183,3 +188,32 @@ class NotificationsScreen extends ConsumerWidget {
}
}
}
class _NotificationsListElement extends ConsumerWidget {
final Profile profile;
final NotificationType type;
final bool isRead;
_NotificationsListElement(
{super.key,
required this.profile,
required this.type,
required this.isRead});
@override
Widget build(BuildContext context, WidgetRef ref) {
final notifications =
ref.watch(userNotificationsProvider(profile, type, isRead));
return ListView.separated(
primary: false,
shrinkWrap: true,
itemBuilder: (_, index) =>
NotificationControl(notification: notifications[index]),
separatorBuilder: (_, __) => const Divider(
color: Colors.black54,
height: 0.0,
),
itemCount: notifications.length,
);
}
}