From 8b25143923751f72f582250311878ee04334b76b Mon Sep 17 00:00:00 2001 From: RanaElgharabawy Date: Sun, 3 May 2026 09:04:20 +0300 Subject: [PATCH 1/3] last min problems fixed --- .../push_notification_service.dart | 2 ++ .../data/models/notification_model.dart | 2 ++ .../domain/entities/notification_entity.dart | 2 +- .../pages/notifications_screen.dart | 1 + .../providers/notifications_provider.dart | 21 +++++++++++++++++-- .../widgets/notification_tile.dart | 6 +++++- 6 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/features/notifications/data/datasources/push_notification_service.dart b/lib/features/notifications/data/datasources/push_notification_service.dart index 22f0a096..8d4b431f 100644 --- a/lib/features/notifications/data/datasources/push_notification_service.dart +++ b/lib/features/notifications/data/datasources/push_notification_service.dart @@ -168,6 +168,8 @@ class PushNotificationService { } else { _route.push('/home/notifications'); } + case 'artist_pro_activated': + return; case 'new_post_by_followed': _route.push('/home/behind-the-track/$resourceId'); case 'new_message': diff --git a/lib/features/notifications/data/models/notification_model.dart b/lib/features/notifications/data/models/notification_model.dart index c4f8c246..edcb3a6d 100644 --- a/lib/features/notifications/data/models/notification_model.dart +++ b/lib/features/notifications/data/models/notification_model.dart @@ -70,6 +70,8 @@ class NotificationModel extends NotificationEntity { return NotificationType.comment; case 'new_post_by_followed': return NotificationType.newPostByFollowed; + case 'artist_pro_activated': + return NotificationType.artistProActivated; default: return NotificationType.newPostByFollowed; } diff --git a/lib/features/notifications/domain/entities/notification_entity.dart b/lib/features/notifications/domain/entities/notification_entity.dart index e4d9a15f..17dea492 100644 --- a/lib/features/notifications/domain/entities/notification_entity.dart +++ b/lib/features/notifications/domain/entities/notification_entity.dart @@ -8,7 +8,7 @@ import 'package:equatable/equatable.dart'; /// - [comment] — a comment was left on a track. /// - [newPostByFollowed] — a followed user uploaded a new track. Filtered out /// at the data layer and never shown in the UI. -enum NotificationType { follow, like, repost, comment, newPostByFollowed } +enum NotificationType { follow, like, repost, comment, newPostByFollowed, artistProActivated } /// The kind of content the notification refers to. /// diff --git a/lib/features/notifications/presentation/pages/notifications_screen.dart b/lib/features/notifications/presentation/pages/notifications_screen.dart index 8eee5102..85defcc9 100644 --- a/lib/features/notifications/presentation/pages/notifications_screen.dart +++ b/lib/features/notifications/presentation/pages/notifications_screen.dart @@ -297,6 +297,7 @@ class _NotificationScreenState extends ConsumerState { case NotificationType.comment: if (embedId != null) context.push('/home/behind-the-track/$embedId'); case NotificationType.newPostByFollowed: + case NotificationType.artistProActivated: return; } } diff --git a/lib/features/notifications/presentation/providers/notifications_provider.dart b/lib/features/notifications/presentation/providers/notifications_provider.dart index 7def79a1..a81e11d2 100644 --- a/lib/features/notifications/presentation/providers/notifications_provider.dart +++ b/lib/features/notifications/presentation/providers/notifications_provider.dart @@ -120,10 +120,17 @@ class NotificationsNotifier extends StateNotifier { try { final result = await _getNotifications(page: 1, type: _activeType); final unread = result.items.where((n) => !n.isRead).toList(); + final hiddenUnread = unread + .where( + (n) => + n.type == NotificationType.artistProActivated || + n.type == NotificationType.newPostByFollowed, + ) + .length; state = state.copyWith( items: result.items, - unreadCount: result.unreadCount, + unreadCount: result.unreadCount - hiddenUnread, hasNext: result.hasNext, currentPage: 1, isLoading: false, @@ -221,11 +228,21 @@ class NotificationsNotifier extends StateNotifier { /// optimistically (if the payload is complete) or falls back to a silent /// re-fetch (when the backend sends a stripped payload without actor info). void onSocketNotificationCreated(Map data) { - state = state.copyWith(unreadCount: state.unreadCount + 1); try { final payload = data['notification'] as Map; final notification = NotificationModel.fromJson(payload); final rawType = payload['type'] as String?; + if (notification.type == NotificationType.artistProActivated || + notification.type == NotificationType.newPostByFollowed) { + _markRead(notification.id).catchError((_) {}); + return; + } + // Skip replays of notifications already in the list + if (state.items.any((n) => n.id == notification.id)) return; + // Only bump the badge for genuinely new unread notifications + if (!notification.isRead) { + state = state.copyWith(unreadCount: state.unreadCount + 1); + } if (_activeType == null || rawType == _activeType) { state = state.copyWith(items: [notification, ...state.items]); return; diff --git a/lib/features/notifications/presentation/widgets/notification_tile.dart b/lib/features/notifications/presentation/widgets/notification_tile.dart index 45b31825..814fac06 100644 --- a/lib/features/notifications/presentation/widgets/notification_tile.dart +++ b/lib/features/notifications/presentation/widgets/notification_tile.dart @@ -29,7 +29,8 @@ class NotificationTile extends StatelessWidget { @override Widget build(BuildContext context) { - if (notification.type == NotificationType.newPostByFollowed) { + if (notification.type == NotificationType.newPostByFollowed || + notification.type == NotificationType.artistProActivated) { return const SizedBox.shrink(); } return InkWell( @@ -136,6 +137,7 @@ class NotificationTile extends StatelessWidget { ], ); case NotificationType.newPostByFollowed: + case NotificationType.artistProActivated: return const SizedBox.shrink(); } } @@ -280,6 +282,7 @@ class _AvatarWithBadge extends StatelessWidget { case NotificationType.repost: return const Color(0xFF00BCD4); case NotificationType.newPostByFollowed: + case NotificationType.artistProActivated: return Colors.transparent; } } @@ -295,6 +298,7 @@ class _AvatarWithBadge extends StatelessWidget { case NotificationType.repost: return Icons.repeat_rounded; case NotificationType.newPostByFollowed: + case NotificationType.artistProActivated: return Icons.notifications_rounded; } } From cd61ae3688669137dcbec00892aab23e700f7499 Mon Sep 17 00:00:00 2001 From: RanaElgharabawy Date: Sun, 3 May 2026 09:34:29 +0300 Subject: [PATCH 2/3] tests fixed --- .../feed/presentation/pages/home_screen.dart | 3 +-- .../providers/notifications_provider.dart | 11 +++-------- .../providers/unread_count_provider.dart | 12 +++++++----- test/features/profile/Data/profile_pages_test.dart | 2 +- .../features/profile/Domain/profile_entity_test.dart | 2 +- .../track/domain/usecases/track_usecases_test.dart | 4 ++-- .../providers/track_interaction_provider_test.dart | 6 +++--- 7 files changed, 18 insertions(+), 22 deletions(-) diff --git a/lib/features/feed/presentation/pages/home_screen.dart b/lib/features/feed/presentation/pages/home_screen.dart index 9ba15aa9..d4d1adfe 100644 --- a/lib/features/feed/presentation/pages/home_screen.dart +++ b/lib/features/feed/presentation/pages/home_screen.dart @@ -166,8 +166,7 @@ class HomeScreen extends ConsumerWidget { ); final unreadMessages = ref.watch(unreadMessagesCountProvider); - final unreadNotifications = - ref.watch(unreadNotificationsCountProvider).asData?.value ?? 0; + final unreadNotifications = ref.watch(unreadNotificationsCountProvider); return Scaffold( key: const Key('home_scaffold'), diff --git a/lib/features/notifications/presentation/providers/notifications_provider.dart b/lib/features/notifications/presentation/providers/notifications_provider.dart index a81e11d2..dd0842d6 100644 --- a/lib/features/notifications/presentation/providers/notifications_provider.dart +++ b/lib/features/notifications/presentation/providers/notifications_provider.dart @@ -120,17 +120,9 @@ class NotificationsNotifier extends StateNotifier { try { final result = await _getNotifications(page: 1, type: _activeType); final unread = result.items.where((n) => !n.isRead).toList(); - final hiddenUnread = unread - .where( - (n) => - n.type == NotificationType.artistProActivated || - n.type == NotificationType.newPostByFollowed, - ) - .length; state = state.copyWith( items: result.items, - unreadCount: result.unreadCount - hiddenUnread, hasNext: result.hasNext, currentPage: 1, isLoading: false, @@ -145,6 +137,9 @@ class NotificationsNotifier extends StateNotifier { for (final n in unread) { _markRead(n.id).catchError((_) {}); } + + // All unread items have been marked as read — drop the badge to zero. + state = state.copyWith(unreadCount: 0); } catch (e) { state = state.copyWith(isLoading: false, error: e.toString()); } diff --git a/lib/features/notifications/presentation/providers/unread_count_provider.dart b/lib/features/notifications/presentation/providers/unread_count_provider.dart index 63e0f80c..0c1c8d13 100644 --- a/lib/features/notifications/presentation/providers/unread_count_provider.dart +++ b/lib/features/notifications/presentation/providers/unread_count_provider.dart @@ -1,8 +1,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:rythmify/features/notifications/domain/usecases/get_unread_count_usecase.dart'; -import 'repo_provider.dart'; +import 'notifications_provider.dart'; -final unreadNotificationsCountProvider = FutureProvider((ref) { - final repo = ref.watch(repositoryprovider); - return GetUnreadCountUsecase(repo).call(); +/// Badge count shown in the app bar / tab bar. +/// +/// Derived from [notificationsProvider] so it drops to zero immediately +/// after the notifications page opens and marks everything as read. +final unreadNotificationsCountProvider = Provider((ref) { + return ref.watch(notificationsProvider).unreadCount; }); diff --git a/test/features/profile/Data/profile_pages_test.dart b/test/features/profile/Data/profile_pages_test.dart index 3bfe00c1..fbbf57dd 100644 --- a/test/features/profile/Data/profile_pages_test.dart +++ b/test/features/profile/Data/profile_pages_test.dart @@ -478,7 +478,7 @@ void main() { expect( find.byKey(const Key('public_profile_play_button')), - findsOneWidget, + findsWidgets, ); }); diff --git a/test/features/profile/Domain/profile_entity_test.dart b/test/features/profile/Domain/profile_entity_test.dart index 9548c25f..a7e35ded 100644 --- a/test/features/profile/Domain/profile_entity_test.dart +++ b/test/features/profile/Domain/profile_entity_test.dart @@ -218,7 +218,7 @@ void main() { group('ProfileEntity — props', () { test('should expose all fields in props', () { - expect(tProfile.props.length, 18); + expect(tProfile.props.length, 19); }); test('should include null fields in props for minimal profile', () { diff --git a/test/features/track/domain/usecases/track_usecases_test.dart b/test/features/track/domain/usecases/track_usecases_test.dart index eab32ccd..ff999b1e 100644 --- a/test/features/track/domain/usecases/track_usecases_test.dart +++ b/test/features/track/domain/usecases/track_usecases_test.dart @@ -143,7 +143,7 @@ void main() { await usecase('track-123', true); - verify(() => mockRepository.toggleLike('track-123', false)).called(1); + verify(() => mockRepository.toggleLike('track-123', true)).called(1); }); test('should return early if id is empty', () async { @@ -168,7 +168,7 @@ void main() { await usecase('track-123', false); - verify(() => mockRepository.toggleRepost('track-123', true)).called(1); + verify(() => mockRepository.toggleRepost('track-123', false)).called(1); }, ); diff --git a/test/features/track/presentation/providers/track_interaction_provider_test.dart b/test/features/track/presentation/providers/track_interaction_provider_test.dart index 9b451868..c6910b96 100644 --- a/test/features/track/presentation/providers/track_interaction_provider_test.dart +++ b/test/features/track/presentation/providers/track_interaction_provider_test.dart @@ -39,7 +39,7 @@ void main() { await notifier.handleToggleLike('track-123', false); - verify(() => mockToggleLike.call('track-123', false)).called(1); + verify(() => mockToggleLike.call('track-123', true)).called(1); }); test('handleToggleLike handles errors gracefully', () async { @@ -62,7 +62,7 @@ void main() { await notifier.handleToggleRepost('track-123', true); - verify(() => mockToggleRepost.call('track-123', true)).called(1); + verify(() => mockToggleRepost.call('track-123', false)).called(1); }); test('handleToggleRepost handles errors gracefully', () async { @@ -74,7 +74,7 @@ void main() { await expectLater( notifier.handleToggleRepost('track-123', true), - completes, + throwsA(isA()), ); }); }); From 5fdd99e00867500a879a11502580cb9e4bfc149d Mon Sep 17 00:00:00 2001 From: RanaElgharabawy Date: Sun, 3 May 2026 09:42:44 +0300 Subject: [PATCH 3/3] lib test --- .../domain/entities/notification_entity.dart | 9 ++++++++- test/features/profile/Data/profile_pages_test.dart | 5 +---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/features/notifications/domain/entities/notification_entity.dart b/lib/features/notifications/domain/entities/notification_entity.dart index 17dea492..a26ca51c 100644 --- a/lib/features/notifications/domain/entities/notification_entity.dart +++ b/lib/features/notifications/domain/entities/notification_entity.dart @@ -8,7 +8,14 @@ import 'package:equatable/equatable.dart'; /// - [comment] — a comment was left on a track. /// - [newPostByFollowed] — a followed user uploaded a new track. Filtered out /// at the data layer and never shown in the UI. -enum NotificationType { follow, like, repost, comment, newPostByFollowed, artistProActivated } +enum NotificationType { + follow, + like, + repost, + comment, + newPostByFollowed, + artistProActivated, +} /// The kind of content the notification refers to. /// diff --git a/test/features/profile/Data/profile_pages_test.dart b/test/features/profile/Data/profile_pages_test.dart index fbbf57dd..19271c01 100644 --- a/test/features/profile/Data/profile_pages_test.dart +++ b/test/features/profile/Data/profile_pages_test.dart @@ -476,10 +476,7 @@ void main() { ), ); - expect( - find.byKey(const Key('public_profile_play_button')), - findsWidgets, - ); + expect(find.byKey(const Key('public_profile_play_button')), findsWidgets); }); testWidgets('should show shuffle button', (tester) async {