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: 3 additions & 0 deletions packages/stream_feeds/lib/src/models.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
export 'models/activity_data.dart';
export 'models/aggregated_activity_data.dart';
export 'models/feed_data.dart';
export 'models/feed_id.dart';
export 'models/feed_input_data.dart';
export 'models/feed_member_data.dart';
export 'models/feed_member_request_data.dart';
export 'models/feeds_config.dart';
export 'models/follow_data.dart';
export 'models/poll_data.dart';
export 'models/request/activity_add_comment_request.dart'
show ActivityAddCommentRequest;
Expand Down
33 changes: 33 additions & 0 deletions packages/stream_feeds/lib/src/models/mark_activity_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,39 @@ class MarkActivityData with _$MarkActivityData {
final List<String>? markWatched;
}

extension MarkActivityDataHandler<R> on MarkActivityData {
R handle({
R Function()? markAllRead,
R Function()? markAllSeen,
R Function(Set<String> read)? markRead,
R Function(Set<String> seen)? markSeen,
R Function(Set<String> watched)? markWatched,
required R Function(MarkActivityData data) orElse,
}) {
if (this.markAllRead case true) {
return markAllRead?.call() ?? orElse(this);
}

if (this.markAllSeen case true) {
return markAllSeen?.call() ?? orElse(this);
}

if (this.markRead case final read?) {
return markRead?.call(read.toSet()) ?? orElse(this);
}

if (this.markSeen case final seen?) {
return markSeen?.call(seen.toSet()) ?? orElse(this);
}

if (this.markWatched case final watched?) {
return markWatched?.call(watched.toSet()) ?? orElse(this);
}

return orElse(this);
}
}

/// Extension function to convert an [ActivityMarkEvent] to a [MarkActivityData] model.
extension ActivityMarkEventMapper on ActivityMarkEvent {
/// Converts this API activity mark event to a domain [MarkActivityData] instance.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'package:collection/collection.dart';
import 'package:stream_core/stream_core.dart';

import '../generated/api/api.dart' as api;
import '../models/activity_data.dart';
import '../models/activity_pin_data.dart';
import '../models/aggregated_activity_data.dart';
import '../models/feed_data.dart';
import '../models/feed_id.dart';
import '../models/feed_member_data.dart';
Expand Down Expand Up @@ -54,7 +56,9 @@ class FeedsRepository {

return GetOrCreateFeedData(
activities: PaginationResult(
items: response.activities.map((a) => a.toModel()).toList(),
items: response.activities
.map((a) => a.toModel())
.sorted(ActivitiesSort.defaultSort.compare),
pagination: PaginationData(
next: response.next,
previous: response.prev,
Expand All @@ -78,6 +82,9 @@ class FeedsRepository {
ownCapabilities: response.ownCapabilities,
pinnedActivities:
response.pinnedActivities.map((a) => a.toModel()).toList(),
aggregatedActivities:
response.aggregatedActivities.map((a) => a.toModel()).toList(),
notificationStatus: response.notificationStatus,
);
});
}
Expand Down
85 changes: 77 additions & 8 deletions packages/stream_feeds/lib/src/state/feed_state.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:math';

import 'package:collection/collection.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:state_notifier/state_notifier.dart';
Expand Down Expand Up @@ -177,14 +179,19 @@ class FeedStateNotifier extends StateNotifier<FeedState> {

/// Handles updates to the feed state when an activity is marked read or seen.
void onActivityMarked(MarkActivityData markData) {
// TODO: Handle activity marking (read/seen/watched) operations

// Note: The mark data contains information about:
// - markAllRead: Whether all activities should be marked as read
// - markAllSeen: Whether all activities should be marked as seen
// - markRead: List of specific activity IDs marked as read
// - markSeen: List of specific activity IDs marked as seen
// - markWatched: List of specific activity IDs marked as watched
// Update the state based on the type of mark operation
state = markData.handle(
// If markAllRead is true, mark all activities as read
markAllRead: () => _markAllRead(state),
// If markAllSeen is true, mark all activities as seen
markAllSeen: () => _markAllSeen(state),
// If markRead contains specific IDs, mark those as read
markRead: (read) => _markRead(read, state),
// If markSeen contains specific IDs, mark those as seen
markSeen: (seen) => _markSeen(seen, state),
// For other cases, return the current state without changes
orElse: (MarkActivityData data) => state,
);
}

/// Handles updates to the feed state when a bookmark is added or removed.
Expand Down Expand Up @@ -376,6 +383,68 @@ class FeedStateNotifier extends StateNotifier<FeedState> {
return _addFollow(follow, removedFollowState);
}

FeedState _markAllRead(FeedState state) {
final aggregatedActivities = state.aggregatedActivities;
final readActivities = aggregatedActivities.map((it) => it.group).toList();

// Set unread count to 0 and update read activities
final updatedNotificationStatus = state.notificationStatus?.copyWith(
unread: 0,
readActivities: readActivities,
lastReadAt: DateTime.timestamp(),
);

return state.copyWith(notificationStatus: updatedNotificationStatus);
}

FeedState _markAllSeen(FeedState state) {
final aggregatedActivities = state.aggregatedActivities;
final seenActivities = aggregatedActivities.map((it) => it.group).toList();

// Set unseen count to 0 and update seen activities
final updatedNotificationStatus = state.notificationStatus?.copyWith(
unseen: 0,
seenActivities: seenActivities,
lastSeenAt: DateTime.timestamp(),
);

return state.copyWith(notificationStatus: updatedNotificationStatus);
}

FeedState _markRead(Set<String> readIds, FeedState state) {
final readActivities = state.notificationStatus?.readActivities?.toSet();
final updatedReadActivities = readActivities?.union(readIds).toList();

// Decrease unread count by the number of newly read activities
final unreadCount = state.notificationStatus?.unread ?? 0;
final updatedUnreadCount = max(unreadCount - readIds.length, 0);

final updatedNotificationStatus = state.notificationStatus?.copyWith(
unread: updatedUnreadCount,
readActivities: updatedReadActivities,
lastReadAt: DateTime.timestamp(),
);

return state.copyWith(notificationStatus: updatedNotificationStatus);
}

FeedState _markSeen(Set<String> seenIds, FeedState state) {
final seenActivities = state.notificationStatus?.seenActivities?.toSet();
final updatedSeenActivities = seenActivities?.union(seenIds).toList();

// Decrease unseen count by the number of newly seen activities
final unseenCount = state.notificationStatus?.unseen ?? 0;
final updatedUnseenCount = max(unseenCount - seenIds.length, 0);

final updatedNotificationStatus = state.notificationStatus?.copyWith(
unseen: updatedUnseenCount,
seenActivities: updatedSeenActivities,
lastSeenAt: DateTime.timestamp(),
);

return state.copyWith(notificationStatus: updatedNotificationStatus);
}

@override
void dispose() {
_removeMemberListListener?.call();
Expand Down
Loading
Loading