Merge pull request #4758 from pangeachat/show-all-activity-sessions
feat: allow admins to view and join ongoing/completed activities
This commit is contained in:
commit
9e8fb2ae27
13 changed files with 362 additions and 60 deletions
|
|
@ -5307,5 +5307,32 @@
|
|||
"vocabLevelsDesc": "This is where vocab words will go once you’ve leveled them up!",
|
||||
"highlightVocabTooltip": "Highlight target vocab words below by sending them or practicing with them in the chat",
|
||||
"teacherModeTitle": "Teacher Mode",
|
||||
"teacherModeDesc": "Toggle to unlock all topics and activities. Course admin only."
|
||||
"teacherModeDesc": "Toggle to unlock all topics and activities. Course admin only.",
|
||||
"notStartedActivitiesTitle": "Open sessions ({num})",
|
||||
"@notStartedActivitiesTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
"num": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inProgressActivitiesTitle": "Happening now ({num})",
|
||||
"@inProgressActivitiesTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
"num": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"completedActivitiesTitle": "Done ({num})",
|
||||
"@completedActivitiesTitle": {
|
||||
"type": "String",
|
||||
"placeholders": {
|
||||
"num": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ class ActivityParticipantList extends StatelessWidget {
|
|||
room: room,
|
||||
builder: (context, participants) {
|
||||
final theme = Theme.of(context);
|
||||
final availableRoles = activity.roles;
|
||||
final availableRoles =
|
||||
activity.roles.values.sorted((a, b) => a.id.compareTo(b.id));
|
||||
|
||||
final remainingMembers = participants.participants.where(
|
||||
(p) => !assignedRoles.values.any((r) => r.userId == p.id),
|
||||
|
|
@ -53,7 +54,7 @@ class ActivityParticipantList extends StatelessWidget {
|
|||
alignment: WrapAlignment.center,
|
||||
spacing: 12.0,
|
||||
runSpacing: 12.0,
|
||||
children: availableRoles.values.map((availableRole) {
|
||||
children: availableRoles.map((availableRole) {
|
||||
final selected =
|
||||
isSelected != null ? isSelected!(availableRole.id) : false;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart
|
|||
import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_sessions_start_view.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_start/bot_join_error_dialog.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/utils/room_summary_extension.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_activities/activity_summaries_provider.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_activities/course_activity_repo.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_activities/course_activity_translation_request.dart';
|
||||
|
|
@ -153,7 +154,7 @@ class ActivitySessionStartController extends State<ActivitySessionStartPage>
|
|||
|
||||
final availableRoles = activity!.roles;
|
||||
final assignedRoles = activityRoom?.assignedRoles ??
|
||||
roomSummaries?[widget.roomId]?.activityRoles.roles ??
|
||||
roomSummaries?[widget.roomId]?.joinedUsersWithRoles ??
|
||||
{};
|
||||
final unassignedIds = availableRoles.keys
|
||||
.where((id) => !assignedRoles.containsKey(id))
|
||||
|
|
@ -187,6 +188,9 @@ class ActivitySessionStartController extends State<ActivitySessionStartPage>
|
|||
return false;
|
||||
}
|
||||
|
||||
Map<ActivitySummaryStatus, Map<String, RoomSummaryResponse>>
|
||||
get activityStatuses => activitySessionStatuses(widget.activityId);
|
||||
|
||||
void toggleInstructions() {
|
||||
setState(() {
|
||||
showInstructions = !showInstructions;
|
||||
|
|
@ -396,6 +400,41 @@ class ActivitySessionStartController extends State<ActivitySessionStartPage>
|
|||
return joinedSessionId;
|
||||
}
|
||||
|
||||
Future<void> joinActivityByRoomId(String roomId) async {
|
||||
final room = Matrix.of(context).client.getRoomById(roomId);
|
||||
if (room != null && room.membership == Membership.join) {
|
||||
widget.parentId != null
|
||||
? context.go("/rooms/spaces/${widget.parentId}/$roomId")
|
||||
: context.go("/rooms/$roomId");
|
||||
return;
|
||||
}
|
||||
|
||||
final resp = await showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
await courseParent!.client.joinRoom(
|
||||
roomId,
|
||||
via: courseParent?.spaceChildren
|
||||
.firstWhereOrNull(
|
||||
(child) => child.roomId == roomId,
|
||||
)
|
||||
?.via,
|
||||
);
|
||||
|
||||
final room = courseParent!.client.getRoomById(roomId);
|
||||
if (room == null || room.membership != Membership.join) {
|
||||
await courseParent!.client.waitForRoomInSync(roomId, join: true);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (!resp.isError) {
|
||||
widget.parentId != null
|
||||
? context.go("/rooms/spaces/${widget.parentId}/$roomId")
|
||||
: context.go("/rooms/$roomId");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> pingCourse() async {
|
||||
if (activityRoom?.courseParent == null) {
|
||||
throw Exception("Activity is not part of a course");
|
||||
|
|
|
|||
|
|
@ -1,21 +1,29 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/config/themes.dart';
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/activity_feedback/activity_feedback_repo.dart';
|
||||
import 'package:fluffychat/pangea/activity_feedback/activity_feedback_request.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_feedback_request_dialog.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_feedback_response_dialog.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_session_start/activity_session_start_page.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_summary_widget.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/utils/room_summary_extension.dart';
|
||||
import 'package:fluffychat/pangea/common/widgets/error_indicator.dart';
|
||||
import 'package:fluffychat/pangea/course_chats/open_roles_indicator.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_activities/activity_summaries_provider.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_activities/course_activity_repo.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/utils/stream_extension.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import 'package:fluffychat/widgets/member_actions_popup_menu_button.dart';
|
||||
|
||||
class ActivitySessionStartView extends StatelessWidget {
|
||||
final ActivitySessionStartController controller;
|
||||
|
|
@ -129,7 +137,6 @@ class ActivitySessionStartView extends StatelessWidget {
|
|||
),
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
spacing: 12.0,
|
||||
children: [
|
||||
ActivitySummary(
|
||||
activity: controller.activity!,
|
||||
|
|
@ -147,10 +154,15 @@ class ActivitySessionStartView extends StatelessWidget {
|
|||
assignedRoles: controller
|
||||
.roomSummaries?[
|
||||
controller.widget.roomId]
|
||||
?.activityRoles
|
||||
.roles ??
|
||||
?.joinedUsersWithRoles ??
|
||||
{},
|
||||
),
|
||||
if (controller.courseParent != null)
|
||||
_ActivityStatuses(
|
||||
statuses: controller.activityStatuses,
|
||||
space: controller.courseParent!,
|
||||
onTap: controller.joinActivityByRoomId,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -393,3 +405,104 @@ class _ActivityStartButtons extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ActivityStatuses extends StatelessWidget {
|
||||
final Map<ActivitySummaryStatus, Map<String, RoomSummaryResponse>> statuses;
|
||||
final Room space;
|
||||
final Function(String) onTap;
|
||||
|
||||
const _ActivityStatuses({
|
||||
required this.statuses,
|
||||
required this.space,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: FluffyThemes.columnWidth * 1.5,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
...ActivitySummaryStatus.values.map(
|
||||
(status) {
|
||||
final entry = statuses[status];
|
||||
if (entry!.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsetsGeometry.symmetric(
|
||||
horizontal: 20.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
status.label(L10n.of(context), entry.length),
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
...entry.entries.map((e) {
|
||||
// if user is in the room, use the room info instead of the
|
||||
// room summary response to get real-time activity roles info
|
||||
final roomId = e.key;
|
||||
final room =
|
||||
Matrix.of(context).client.getRoomById(roomId);
|
||||
|
||||
final activityPlan =
|
||||
room?.activityPlan ?? e.value.activityPlan;
|
||||
|
||||
// If activity is completed, show all roles, even for users who have left the
|
||||
// room (like the bot). Otherwise, show only joined users with roles
|
||||
Map<String, ActivityRoleModel> activityRoles =
|
||||
status == ActivitySummaryStatus.completed
|
||||
? e.value.activityRoles.roles
|
||||
: e.value.joinedUsersWithRoles;
|
||||
|
||||
// If the user is in the activity room and it's not completed, use the room's
|
||||
// state events to determine roles to update them in real-time
|
||||
if (room?.assignedRoles != null &&
|
||||
status != ActivitySummaryStatus.completed) {
|
||||
activityRoles = room!.assignedRoles!;
|
||||
}
|
||||
|
||||
return ListTile(
|
||||
title: OpenRolesIndicator(
|
||||
roles: activityPlan.roles.values
|
||||
.sorted((a, b) => a.id.compareTo(b.id))
|
||||
.toList(),
|
||||
assignedRoles: activityRoles.values.toList(),
|
||||
size: 40.0,
|
||||
spacing: 8.0,
|
||||
space: space,
|
||||
onUserTap: (user, context) {
|
||||
showMemberActionsPopupMenu(
|
||||
context: context,
|
||||
user: user,
|
||||
);
|
||||
},
|
||||
),
|
||||
trailing: space.isRoomAdmin
|
||||
? const Icon(Icons.arrow_forward)
|
||||
: null,
|
||||
onTap: space.isRoomAdmin ? () => onTap(roomId) : null,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,10 +66,8 @@ class ChatListItemSubtitle extends StatelessWidget {
|
|||
);
|
||||
} else if (!room.isActivityStarted) {
|
||||
return OpenRolesIndicator(
|
||||
totalSlots: room.activityPlan!.req.numberOfParticipants,
|
||||
userIds:
|
||||
room.activityRoles?.roles.values.map((r) => r.userId).toList() ??
|
||||
[],
|
||||
roles: room.activityPlan!.roles.values.toList(),
|
||||
assignedRoles: room.assignedRoles?.values.toList() ?? [],
|
||||
room: room,
|
||||
space: room.courseParent,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:matrix/matrix.dart';
|
|||
import 'package:matrix/matrix_api_lite/generated/api.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_roles_model.dart';
|
||||
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
|
||||
|
||||
|
|
@ -64,12 +65,31 @@ class RoomSummariesResponse {
|
|||
class RoomSummaryResponse {
|
||||
final ActivityPlanModel activityPlan;
|
||||
final ActivityRolesModel activityRoles;
|
||||
final Map<String, String> membershipSummary;
|
||||
|
||||
RoomSummaryResponse({
|
||||
required this.activityPlan,
|
||||
required this.activityRoles,
|
||||
required this.membershipSummary,
|
||||
});
|
||||
|
||||
Membership? getMembershipForUserId(String userId) {
|
||||
final membershipString = membershipSummary[userId];
|
||||
if (membershipString == null) return null;
|
||||
return Membership.values.firstWhere(
|
||||
(m) => m.name == membershipString,
|
||||
orElse: () => Membership.join,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, ActivityRoleModel> get joinedUsersWithRoles {
|
||||
return Map.fromEntries(
|
||||
activityRoles.roles.entries.where(
|
||||
(role) => getMembershipForUserId(role.value.userId) == Membership.join,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
factory RoomSummaryResponse.fromJson(Map<String, dynamic> json) {
|
||||
return RoomSummaryResponse(
|
||||
activityPlan: ActivityPlanModel.fromJson(
|
||||
|
|
@ -78,6 +98,9 @@ class RoomSummaryResponse {
|
|||
activityRoles: ActivityRolesModel.fromJson(
|
||||
json[PangeaEventTypes.activityRole]?["default"]?["content"] ?? {},
|
||||
),
|
||||
membershipSummary: Map<String, String>.from(
|
||||
json['membership_summary'] ?? {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -85,6 +108,7 @@ class RoomSummaryResponse {
|
|||
return {
|
||||
PangeaEventTypes.activityPlan: activityPlan.toJson(),
|
||||
PangeaEventTypes.activityRole: activityRoles.toJson(),
|
||||
'membership_summary': membershipSummary,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,6 +252,12 @@ void chatContextMenuAction(
|
|||
context: context,
|
||||
future: room.isSpace ? room.leaveSpace : room.leave,
|
||||
);
|
||||
|
||||
final r = room.client.getRoomById(room.id);
|
||||
if (r != null && r.membership != Membership.leave) {
|
||||
await room.client.waitForRoomInSync(room.id, leave: true);
|
||||
}
|
||||
|
||||
if (!resp.isError) {
|
||||
outerContext.go(
|
||||
room.courseParent != null
|
||||
|
|
|
|||
|
|
@ -97,8 +97,8 @@ class ActivityTemplateChatListItem extends StatelessWidget {
|
|||
children: [
|
||||
Expanded(
|
||||
child: OpenRolesIndicator(
|
||||
totalSlots: activity.req.numberOfParticipants,
|
||||
userIds: e.userIds,
|
||||
roles: activity.roles.values.toList(),
|
||||
assignedRoles: e.assignedRoles,
|
||||
space: space,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -131,8 +131,7 @@ class CourseChatsController extends State<CourseChats>
|
|||
}
|
||||
|
||||
final activity = summary.activityPlan;
|
||||
final users =
|
||||
summary.activityRoles.roles.values.map((r) => r.userId).toList();
|
||||
final users = summary.joinedUsersWithRoles;
|
||||
|
||||
if (users.isEmpty || !validIDs.contains(activity.activityId)) {
|
||||
continue;
|
||||
|
|
@ -155,7 +154,7 @@ class CourseChatsController extends State<CourseChats>
|
|||
sessionsMap[activity]!.add(
|
||||
ExtendedSpaceRoomsChunk(
|
||||
chunk: chunk,
|
||||
userIds: users,
|
||||
assignedRoles: users.values.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -454,7 +453,9 @@ class CourseChatsController extends State<CourseChats>
|
|||
String activityId,
|
||||
ExtendedSpaceRoomsChunk chunk,
|
||||
) async {
|
||||
final hasRole = chunk.userIds.contains(widget.client.userID);
|
||||
final hasRole = chunk.assignedRoles.any(
|
||||
(role) => role.userId == widget.client.userID,
|
||||
);
|
||||
final roomId = chunk.chunk.roomId;
|
||||
if (!hasRole) {
|
||||
context.go(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
|
||||
|
||||
class ExtendedSpaceRoomsChunk {
|
||||
final SpaceRoomsChunk chunk;
|
||||
final List<String> userIds;
|
||||
final List<ActivityRoleModel> assignedRoles;
|
||||
|
||||
ExtendedSpaceRoomsChunk({
|
||||
required this.chunk,
|
||||
required this.userIds,
|
||||
required this.assignedRoles,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +1,75 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_planner/activity_plan_model.dart';
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_role_model.dart';
|
||||
import 'package:fluffychat/widgets/avatar.dart';
|
||||
|
||||
class OpenRolesIndicator extends StatelessWidget {
|
||||
final int totalSlots;
|
||||
final List<String> userIds;
|
||||
final List<ActivityRole> roles;
|
||||
final List<ActivityRoleModel> assignedRoles;
|
||||
final Room? room;
|
||||
final Room? space;
|
||||
|
||||
final double? spacing;
|
||||
final double? size;
|
||||
final Function(User, BuildContext)? onUserTap;
|
||||
|
||||
const OpenRolesIndicator({
|
||||
super.key,
|
||||
required this.totalSlots,
|
||||
required this.userIds,
|
||||
required this.roles,
|
||||
required this.assignedRoles,
|
||||
this.room,
|
||||
this.space,
|
||||
this.spacing,
|
||||
this.size,
|
||||
this.onUserTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final remainingSlots = max(
|
||||
0,
|
||||
totalSlots - userIds.length,
|
||||
);
|
||||
|
||||
final roomParticipants = room?.getParticipants() ?? [];
|
||||
final spaceParticipants = space?.getParticipants() ?? [];
|
||||
|
||||
return Row(
|
||||
spacing: 2.0,
|
||||
spacing: spacing ?? 2.0,
|
||||
children: [
|
||||
...userIds.map((u) {
|
||||
final user = roomParticipants.firstWhereOrNull((p) => p.id == u) ??
|
||||
spaceParticipants.firstWhereOrNull((p) => p.id == u);
|
||||
...roles.map((role) {
|
||||
final assigned =
|
||||
assignedRoles.firstWhereOrNull((r) => r.id == role.id);
|
||||
|
||||
return Avatar(
|
||||
mxContent: user?.avatarUrl,
|
||||
name: user?.calcDisplayname() ?? u.localpart ?? u,
|
||||
size: 16,
|
||||
userId: u,
|
||||
);
|
||||
}),
|
||||
...List.generate(remainingSlots, (_) {
|
||||
return Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 8,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
final user = assigned != null
|
||||
? roomParticipants
|
||||
.firstWhereOrNull((p) => p.id == assigned.userId) ??
|
||||
spaceParticipants
|
||||
.firstWhereOrNull((p) => p.id == assigned.userId)
|
||||
: null;
|
||||
|
||||
if (assigned != null) {
|
||||
return Builder(
|
||||
builder: (context) => Avatar(
|
||||
mxContent: user?.avatarUrl,
|
||||
name: user?.calcDisplayname() ??
|
||||
assigned.userId.localpart ??
|
||||
assigned.userId,
|
||||
size: size ?? 16,
|
||||
userId: assigned.userId,
|
||||
onTap: onUserTap != null && user != null
|
||||
? () => onUserTap!(user, context)
|
||||
: null,
|
||||
),
|
||||
CircleAvatar(
|
||||
radius: 7,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return CircleAvatar(
|
||||
radius: size != null ? size! / 2 : 8,
|
||||
backgroundColor: Theme.of(context).colorScheme.primaryContainer,
|
||||
child: Icon(
|
||||
Icons.question_mark,
|
||||
size: size != null ? (size! / 2) : 8,
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -2,12 +2,30 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/pangea/bot/utils/bot_name.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/utils/room_summary_extension.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/course_topics/course_topic_model.dart';
|
||||
import 'package:fluffychat/pangea/course_plans/courses/course_plan_model.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
|
||||
enum ActivitySummaryStatus {
|
||||
notStarted,
|
||||
inProgress,
|
||||
completed;
|
||||
|
||||
String label(L10n l10n, int count) {
|
||||
switch (this) {
|
||||
case ActivitySummaryStatus.notStarted:
|
||||
return l10n.notStartedActivitiesTitle(count);
|
||||
case ActivitySummaryStatus.inProgress:
|
||||
return l10n.inProgressActivitiesTitle(count);
|
||||
case ActivitySummaryStatus.completed:
|
||||
return l10n.completedActivitiesTitle(count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mixin ActivitySummariesProvider<T extends StatefulWidget> on State<T> {
|
||||
Map<String, RoomSummaryResponse>? roomSummaries;
|
||||
|
||||
|
|
@ -23,6 +41,72 @@ mixin ActivitySummariesProvider<T extends StatefulWidget> on State<T> {
|
|||
}
|
||||
}
|
||||
|
||||
bool isActivityStarted(String roomId) {
|
||||
if (isActivityFinished(roomId)) return true;
|
||||
final roomSummary = roomSummaries?[roomId];
|
||||
if (roomSummary == null) return false;
|
||||
|
||||
final activityPlan = roomSummary.activityPlan;
|
||||
final assignedRoles = roomSummary.joinedUsersWithRoles;
|
||||
return activityPlan.roles.length - assignedRoles.length <= 0;
|
||||
}
|
||||
|
||||
bool isActivityFinished(String roomId) {
|
||||
final roomSummary = roomSummaries?[roomId];
|
||||
if (roomSummary == null) return false;
|
||||
|
||||
final activityRoles = roomSummary.activityRoles;
|
||||
final roles = activityRoles.roles.values.where(
|
||||
(r) => r.userId != BotName.byEnvironment,
|
||||
);
|
||||
|
||||
if (roles.isEmpty) return false;
|
||||
if (!roles.any((r) => r.isFinished)) return false;
|
||||
|
||||
return roles.every((r) {
|
||||
if (r.isFinished) return true;
|
||||
|
||||
// if the user is in the chat (not null && membership is join),
|
||||
// then the activity is not finished for them
|
||||
final membership = roomSummary.getMembershipForUserId(r.userId);
|
||||
return membership == null || membership != Membership.join;
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, RoomSummaryResponse> activitySessions(String activityId) =>
|
||||
Map.fromEntries(
|
||||
roomSummaries?.entries
|
||||
.where((v) => v.value.activityPlan.activityId == activityId) ??
|
||||
[],
|
||||
);
|
||||
|
||||
Map<ActivitySummaryStatus, Map<String, RoomSummaryResponse>>
|
||||
activitySessionStatuses(
|
||||
String activityId,
|
||||
) {
|
||||
final statuses = <ActivitySummaryStatus, Map<String, RoomSummaryResponse>>{
|
||||
ActivitySummaryStatus.notStarted: {},
|
||||
ActivitySummaryStatus.inProgress: {},
|
||||
ActivitySummaryStatus.completed: {},
|
||||
};
|
||||
|
||||
final sessions = activitySessions(activityId);
|
||||
for (final entry in sessions.entries) {
|
||||
final session = entry.value;
|
||||
final roomId = entry.key;
|
||||
|
||||
if (isActivityFinished(roomId)) {
|
||||
statuses[ActivitySummaryStatus.completed]![roomId] = session;
|
||||
} else if (isActivityStarted(roomId)) {
|
||||
statuses[ActivitySummaryStatus.inProgress]![roomId] = session;
|
||||
} else {
|
||||
statuses[ActivitySummaryStatus.notStarted]![roomId] = session;
|
||||
}
|
||||
}
|
||||
|
||||
return statuses;
|
||||
}
|
||||
|
||||
Set<String> openSessions(String activityId) {
|
||||
if (roomSummaries == null || roomSummaries!.isEmpty) return {};
|
||||
final Set<String> sessions = {};
|
||||
|
|
@ -35,12 +119,7 @@ mixin ActivitySummariesProvider<T extends StatefulWidget> on State<T> {
|
|||
continue;
|
||||
}
|
||||
|
||||
final isOpen =
|
||||
!summary.activityRoles.roles.values.any((r) => r.isArchived) &&
|
||||
(summary.activityRoles.roles.length <
|
||||
summary.activityPlan.req.numberOfParticipants);
|
||||
|
||||
if (isOpen) {
|
||||
if (!isActivityStarted(roomId)) {
|
||||
sessions.add(roomId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ description: Learn a language while texting your friends.
|
|||
# Pangea#
|
||||
publish_to: none
|
||||
# On version bump also increase the build number for F-Droid
|
||||
version: 4.1.15+9
|
||||
version: 4.1.15+10
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue