Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions lib/features/feed/presentation/pages/home_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
enum NotificationType {
follow,
like,
repost,
comment,
newPostByFollowed,
artistProActivated,
}

/// The kind of content the notification refers to.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ class _NotificationScreenState extends ConsumerState<NotificationsScreen> {
case NotificationType.comment:
if (embedId != null) context.push('/home/behind-the-track/$embedId');
case NotificationType.newPostByFollowed:
case NotificationType.artistProActivated:
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ class NotificationsNotifier extends StateNotifier<NotificationsState> {

state = state.copyWith(
items: result.items,
unreadCount: result.unreadCount,
hasNext: result.hasNext,
currentPage: 1,
isLoading: false,
Expand All @@ -138,6 +137,9 @@ class NotificationsNotifier extends StateNotifier<NotificationsState> {
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());
}
Expand Down Expand Up @@ -221,11 +223,21 @@ class NotificationsNotifier extends StateNotifier<NotificationsState> {
/// 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<String, dynamic> data) {
state = state.copyWith(unreadCount: state.unreadCount + 1);
try {
final payload = data['notification'] as Map<String, dynamic>;
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<int>((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<int>((ref) {
return ref.watch(notificationsProvider).unreadCount;
});
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -136,6 +137,7 @@ class NotificationTile extends StatelessWidget {
],
);
case NotificationType.newPostByFollowed:
case NotificationType.artistProActivated:
return const SizedBox.shrink();
}
}
Expand Down Expand Up @@ -280,6 +282,7 @@ class _AvatarWithBadge extends StatelessWidget {
case NotificationType.repost:
return const Color(0xFF00BCD4);
case NotificationType.newPostByFollowed:
case NotificationType.artistProActivated:
return Colors.transparent;
}
}
Expand All @@ -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;
}
}
Expand Down
5 changes: 1 addition & 4 deletions test/features/profile/Data/profile_pages_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -476,10 +476,7 @@ void main() {
),
);

expect(
find.byKey(const Key('public_profile_play_button')),
findsOneWidget,
);
expect(find.byKey(const Key('public_profile_play_button')), findsWidgets);
});

testWidgets('should show shuffle button', (tester) async {
Expand Down
2 changes: 1 addition & 1 deletion test/features/profile/Domain/profile_entity_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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', () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
},
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -74,7 +74,7 @@ void main() {

await expectLater(
notifier.handleToggleRepost('track-123', true),
completes,
throwsA(isA<Exception>()),
);
});
});
Expand Down
Loading