Skip to content

Commit 942d955

Browse files
authored
Merge pull request #3127 from acterglobal/anisha/activity-showcase
Activity List UI Showcase with mock data.
2 parents 9045652 + bd967ed commit 942d955

22 files changed

+1013
-56
lines changed

app/lib/features/activities/providers/activities_providers.dart

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,13 @@ final hasUnconfirmedEmailAddresses = StateProvider(
6767
true,
6868
);
6969

70+
final activityProvider =
71+
AsyncNotifierProviderFamily<AsyncActivityNotifier, Activity?, String>(
72+
AsyncActivityNotifier.new,
73+
);
74+
7075
final allActivitiesProvider =
71-
AsyncNotifierProvider<AllActivitiesNotifier, List<Activity>>(
76+
AsyncNotifierProvider<AllActivitiesNotifier, List<String>>(
7277
AllActivitiesNotifier.new,
7378
);
7479

@@ -78,18 +83,37 @@ DateTime getActivityDate(int timestamp) {
7883
return DateTime(activityDate.year, activityDate.month, activityDate.day);
7984
}
8085

86+
// Provider to get activities by id
87+
final allActivitiesByIdProvider = FutureProvider<List<Activity>>((ref) async {
88+
89+
final activityIds = await ref.watch(allActivitiesProvider.future);
90+
91+
if (activityIds.isEmpty) return [];
92+
93+
final activities = <Activity>[];
94+
for (final id in activityIds) {
95+
final activity = ref.watch(activityProvider(id)).valueOrNull;
96+
if (activity != null) {
97+
activities.add(activity);
98+
}
99+
}
100+
return activities;
101+
});
102+
81103
final activityDatesProvider = Provider<List<DateTime>>((ref) {
82-
final activities = ref.watch(allActivitiesProvider).valueOrNull;
83-
if (activities == null || activities.isEmpty) return [];
104+
final activities = ref.watch(allActivitiesByIdProvider).valueOrNull ?? [];
105+
106+
if (activities.isEmpty) return [];
84107

85108
final uniqueDates = activities.map((activity) => getActivityDate(activity.originServerTs())).toSet();
86109
return uniqueDates.toList()..sort((a, b) => b.compareTo(a));
87110
});
88111

89112
// Base provider for activities filtered by date
90113
final activitiesByDateProvider = Provider.family<List<Activity>, DateTime>((ref, date) {
91-
final allActivities = ref.watch(allActivitiesProvider).valueOrNull ?? [];
92-
return allActivities.where((activity) => getActivityDate(activity.originServerTs()).isAtSameMomentAs(date)).toList();
114+
final activities = ref.watch(allActivitiesByIdProvider).valueOrNull ?? [];
115+
return activities.where((activity) =>
116+
getActivityDate(activity.originServerTs()).isAtSameMomentAs(date)).toList();
93117
});
94118

95119
// Provider for consecutive grouped activities using records

app/lib/features/activities/providers/notifiers/activities_notifiers.dart

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,76 @@
11
import 'dart:async';
22
import 'package:acter/common/providers/space_providers.dart';
3+
import 'package:acter/common/utils/utils.dart';
4+
import 'package:acter/config/constants.dart';
5+
import 'package:acter/features/activity_ui_showcase/mocks/providers/mock_activities_provider.dart';
36
import 'package:acter/features/home/providers/client_providers.dart';
47
import 'package:acter_flutter_sdk/acter_flutter_sdk_ffi.dart'
58
show Activities, Activity, Client;
9+
import 'package:logging/logging.dart';
610
import 'package:riverpod/riverpod.dart';
711

8-
class AllActivitiesNotifier extends AsyncNotifier<List<Activity>> {
12+
final _log = Logger('a3::activities::notifiers');
13+
14+
// Single Activity Notifier
15+
class AsyncActivityNotifier extends FamilyAsyncNotifier<Activity?, String> {
16+
late Stream<bool> _listener;
17+
late StreamSubscription<bool> _poller;
18+
19+
@override
20+
FutureOr<Activity?> build(String arg) async {
21+
final activityId = arg;
22+
23+
// if we are in showcase mode, return mock activity
24+
if (includeShowCases) {
25+
final mockActivity = ref.watch(mockActivityProvider(activityId));
26+
if (mockActivity != null) {
27+
return mockActivity;
28+
}
29+
}
30+
31+
// otherwise, get the activity from the client
32+
final client = await ref.watch(alwaysClientProvider.future);
33+
_listener = client.subscribeModelStream(
34+
activityId,
35+
); // keep it resident in memory
36+
_poller = _listener.listen(
37+
(data) async {
38+
try {
39+
state = AsyncValue.data(await client.activity(activityId));
40+
} catch (e, s) {
41+
_log.severe('activity stream update failed', e, s);
42+
state = AsyncValue.error(e, s);
43+
}
44+
},
45+
onError: (e, s) {
46+
_log.severe('activity stream errored', e, s);
47+
state = AsyncValue.error(e, s);
48+
},
49+
onDone: () {
50+
_log.info('activity stream ended');
51+
},
52+
);
53+
ref.onDispose(() => _poller.cancel());
54+
return await client.activity(activityId);
55+
}
56+
}
57+
58+
class AllActivitiesNotifier extends AsyncNotifier<List<String>> {
959
final List<StreamSubscription> _subscriptions = [];
1060
Activities? _activities;
1161

12-
Future<List<Activity>> _fetchAllActivities(Client client) async {
62+
Future<List<String>> _fetchAllActivities(Client client) async {
1363
_activities?.drop();
1464
_activities = null; // Prevent double free
1565
_activities = client.allActivities();
1666
final activityIds = await _activities?.getIds(0, 200); // adjust as needed
1767
if (activityIds == null) return [];
18-
final activities = await Future.wait(
19-
activityIds.toList().map((id) => client.activity(id.toDartString())),
20-
);
21-
return activities.whereType<Activity>().toList();
68+
69+
return asDartStringList(activityIds);
2270
}
2371

2472
@override
25-
Future<List<Activity>> build() async {
73+
Future<List<String>> build() async {
2674
final client = await ref.watch(alwaysClientProvider.future);
2775
final spaces = ref.watch(spacesProvider);
2876

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import 'package:acter/features/activity_ui_showcase/mocks/general/mock_activity.dart';
2+
import 'package:acter/features/activity_ui_showcase/mocks/general/mock_activity_object.dart';
3+
import 'package:acter/features/activity_ui_showcase/mocks/general/mock_description_content.dart';
4+
import 'package:acter/features/activity_ui_showcase/mocks/general/mock_ref_details.dart';
5+
import 'package:acter/features/activity_ui_showcase/mocks/general/mock_room_topic_content.dart';
6+
import 'package:acter/features/activity_ui_showcase/mocks/general/mock_title_content.dart';
7+
import 'package:acter/features/chat_ui_showcase/mocks/general/mock_msg_content.dart';
8+
import 'package:acter_notifify/model/push_styles.dart';
9+
10+
final now = DateTime.now();
11+
12+
final pinCommentActivity1 = MockActivity(
13+
mockActivityId: 'pin-comment-activity-bigvis-1',
14+
mockType: PushStyles.comment.name,
15+
mockSubType: 'pin',
16+
mockSenderId: 'alice',
17+
mockRoomId: 'development-team',
18+
mockOriginServerTs:
19+
now.subtract(const Duration(minutes: 5)).millisecondsSinceEpoch,
20+
mockObject: MockActivityObject(
21+
mockObjectId: 'pin-project-proposal-object-1',
22+
mockType: 'pin',
23+
mockTitle: 'Project Proposal Document',
24+
),
25+
mockMsgContent: MockMsgContent(
26+
mockBody:
27+
'Great insights on the technical approach! Looking forward to implementation.',
28+
),
29+
);
30+
31+
final documentAttachmentActivity1 = MockActivity(
32+
mockActivityId: 'document-attachment-activity-bigvis-1',
33+
mockType: PushStyles.attachment.name,
34+
mockSubType: 'document',
35+
mockSenderId: 'charlie',
36+
mockRoomId: 'development-team',
37+
mockOriginServerTs:
38+
now.subtract(const Duration(minutes: 25)).millisecondsSinceEpoch,
39+
mockObject: MockActivityObject(
40+
mockObjectId: 'pin-sprint-planning-object-1',
41+
mockType: 'pin',
42+
mockTitle: 'Sprint Planning Notes',
43+
),
44+
mockMsgContent: MockMsgContent(mockBody: 'Meeting notes and action items'),
45+
);
46+
47+
final eventReferenceActivity1 = MockActivity(
48+
mockActivityId: 'event-reference-activity-bigvis-1',
49+
mockType: PushStyles.references.name,
50+
mockSubType: 'event_reference',
51+
mockSenderId: 'david',
52+
mockRoomId: 'development-team',
53+
mockOriginServerTs:
54+
now.subtract(const Duration(minutes: 25)).millisecondsSinceEpoch,
55+
mockObject: MockActivityObject(
56+
mockObjectId: 'event-sprint-planning-object-1',
57+
mockType: 'event',
58+
mockTitle: 'Event',
59+
),
60+
mockMsgContent: MockMsgContent(mockBody: 'Event Description'),
61+
mockRefDetails: MockRefDetails(
62+
mockTitle: 'Sprint Planning Meeting',
63+
mockType: 'calendar-event',
64+
mockTargetId: 'sprint-planning-event-id',
65+
),
66+
);
67+
68+
final taskAddActivity1 = MockActivity(
69+
mockActivityId: 'task-add-activity-bigvis-1',
70+
mockType: PushStyles.taskAdd.name,
71+
mockSubType: 'task_list',
72+
mockSenderId: 'elena',
73+
mockRoomId: 'development-team',
74+
mockOriginServerTs:
75+
now.subtract(const Duration(minutes: 45)).millisecondsSinceEpoch,
76+
mockName: 'Code Review Guidelines',
77+
mockObject: MockActivityObject(
78+
mockObjectId: 'task-list-code-review-object-1',
79+
mockType: 'task-list',
80+
mockTitle: 'Code Review Process',
81+
),
82+
);
83+
84+
final taskTitleChangeActivity1 = MockActivity(
85+
mockActivityId: 'task-title-change-activity-bigvis-1',
86+
mockType: PushStyles.titleChange.name,
87+
mockSubType: 'task_title_change',
88+
mockSenderId: 'elena',
89+
mockRoomId: 'development-team',
90+
mockOriginServerTs:
91+
now.subtract(const Duration(minutes: 50)).millisecondsSinceEpoch,
92+
mockObject: MockActivityObject(
93+
mockObjectId: 'task-list-code-review-object-2',
94+
mockType: 'task-list',
95+
mockTitle: 'Code Review Process',
96+
),
97+
mockTitleContent: MockTitleContent(
98+
mockChange: 'Changed',
99+
mockNewVal: 'Code Review Process',
100+
),
101+
);
102+
103+
final roomTopicChangeActivity1 = MockActivity(
104+
mockActivityId: 'room-topic-change-activity-bigvis-1',
105+
mockType: PushStyles.roomTopic.name,
106+
mockSubType: 'space_description',
107+
mockSenderId: 'diana',
108+
mockRoomId: 'development-team',
109+
mockOriginServerTs:
110+
now.subtract(const Duration(minutes: 35)).millisecondsSinceEpoch,
111+
mockName:
112+
'Updated space description to reflect new project scope and objectives',
113+
mockRoomTopicContent: MockRoomTopicContent(
114+
mockChange: 'Changed',
115+
mockNewVal:
116+
'A collaborative space for the development team to plan, discuss, and execute technical projects. We focus on code quality, innovative solutions, and continuous improvement.',
117+
mockOldVal: 'Development team workspace',
118+
),
119+
);
120+
121+
final descriptionChangeActivity1 = MockActivity(
122+
mockActivityId: 'description-change-activity-bigvis-1',
123+
mockType: PushStyles.descriptionChange.name,
124+
mockSubType: 'pin_description_change',
125+
mockSenderId: 'quinn',
126+
mockRoomId: 'community-hub',
127+
mockOriginServerTs:
128+
now
129+
.subtract(const Duration(hours: 12, minutes: 15))
130+
.millisecondsSinceEpoch,
131+
mockObject: MockActivityObject(
132+
mockObjectId: 'pin-community-guidelines-object-1',
133+
mockType: 'pin',
134+
mockTitle: 'Community Guidelines',
135+
),
136+
mockDescriptionContent: MockDescriptionContent(
137+
mockChange: 'Changed',
138+
mockNewVal:
139+
'Updated community guidelines to reflect our growing membership and new collaboration tools. Please review the latest changes and ensure all team members are aware.',
140+
),
141+
);
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import 'package:acter/features/activity_ui_showcase/mocks/general/mock_activity.dart';
2+
import 'package:acter/features/activity_ui_showcase/mocks/general/mock_activity_object.dart';
3+
import 'package:acter_notifify/model/push_styles.dart';
4+
5+
final now = DateTime.now();
6+
7+
final eventCreationActivity1 = MockActivity(
8+
mockActivityId: 'event-creation-activity-indiv-1',
9+
mockType: PushStyles.creation.name,
10+
mockSubType: 'event_created',
11+
mockSenderId: 'frank',
12+
mockRoomId: 'marketing-team',
13+
mockOriginServerTs:
14+
now.subtract(const Duration(hours: 1)).millisecondsSinceEpoch,
15+
mockObject: MockActivityObject(
16+
mockObjectId: 'event-q1-campaign-launch-object-1',
17+
mockType: 'event',
18+
mockTitle: 'Q1 Campaign Launch',
19+
),
20+
);
21+
22+
final rsvpYesActivity1 = MockActivity(
23+
mockActivityId: 'rsvp-yes-activity-indiv-1',
24+
mockType: PushStyles.rsvpYes.name,
25+
mockSubType: 'event_rsvp',
26+
mockSenderId: 'grace',
27+
mockRoomId: 'marketing-team',
28+
mockOriginServerTs:
29+
now.subtract(const Duration(hours: 2)).millisecondsSinceEpoch,
30+
mockObject: MockActivityObject(
31+
mockObjectId: 'event-brand-strategy-workshop-object-1',
32+
mockType: 'event',
33+
mockTitle: 'Brand Strategy Workshop',
34+
),
35+
);
36+
37+
final rsvpMaybeActivity1 = MockActivity(
38+
mockActivityId: 'rsvp-maybe-activity-indiv-1',
39+
mockType: PushStyles.rsvpMaybe.name,
40+
mockSubType: 'event_rsvp',
41+
mockSenderId: 'henry',
42+
mockRoomId: 'marketing-team',
43+
mockOriginServerTs:
44+
now.subtract(const Duration(hours: 3)).millisecondsSinceEpoch,
45+
mockObject: MockActivityObject(
46+
mockObjectId: 'event-client-feedback-session-object-1',
47+
mockType: 'event',
48+
mockTitle: 'Client Feedback Session',
49+
),
50+
);
51+
52+
final taskCompleteActivity1 = MockActivity(
53+
mockActivityId: 'task-complete-activity-indiv-1',
54+
mockType: PushStyles.taskComplete.name,
55+
mockSubType: 'task_status',
56+
mockSenderId: 'jack',
57+
mockRoomId: 'marketing-team',
58+
mockOriginServerTs:
59+
now.subtract(const Duration(hours: 5)).millisecondsSinceEpoch,
60+
mockObject: MockActivityObject(
61+
mockObjectId: 'task-social-media-calendar-object-1',
62+
mockType: 'task',
63+
mockTitle: 'Social Media Calendar',
64+
),
65+
);
66+
67+
final taskReopenActivity1 = MockActivity(
68+
mockActivityId: 'task-reopen-activity-indiv-1',
69+
mockType: PushStyles.taskReOpen.name,
70+
mockSubType: 'task_status',
71+
mockSenderId: 'kelly',
72+
mockRoomId: 'marketing-team',
73+
mockOriginServerTs:
74+
now.subtract(const Duration(hours: 6)).millisecondsSinceEpoch,
75+
mockObject: MockActivityObject(
76+
mockObjectId: 'task-website-content-update-object-1',
77+
mockType: 'task',
78+
mockTitle: 'Website Content Update',
79+
),
80+
);

0 commit comments

Comments
 (0)