merge prod into main

This commit is contained in:
ggurdin 2025-12-03 10:04:43 -05:00
commit c3917e3490
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
8 changed files with 112 additions and 25 deletions

View file

@ -42,7 +42,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),
@ -55,7 +56,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;

View file

@ -161,7 +161,7 @@ class ActivitySessionStartController extends State<ActivitySessionStartPage>
if (activityRoom != null && activityRoom!.membership == Membership.join) {
return activityRoom!.assignedRoles ?? {};
}
return roomSummaries?[widget.roomId]?.activityRoles.roles ?? {};
return roomSummaries?[widget.roomId]?.joinedUsersWithRoles ?? {};
}
bool canSelectParticipant(String id) {
@ -172,7 +172,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))
@ -441,6 +441,14 @@ class ActivitySessionStartController extends State<ActivitySessionStartPage>
}
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 {

View file

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:go_router/go_router.dart';
import 'package:matrix/matrix.dart';
@ -7,6 +8,8 @@ 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_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';
@ -459,13 +462,35 @@ class _ActivityStatuses extends StatelessWidget {
),
),
...entry.entries.map((e) {
final summary = e.value;
// 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: summary.activityPlan.roles.values.toList(),
assignedRoles:
summary.activityRoles.roles.values.toList(),
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,

View file

@ -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,
};
}
}

View file

@ -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

View file

@ -132,7 +132,7 @@ class CourseChatsController extends State<CourseChats>
}
final activity = summary.activityPlan;
final users = summary.activityRoles.roles.values.toList();
final users = summary.joinedUsersWithRoles;
if (users.isEmpty || !validIDs.contains(activity.activityId)) {
continue;
@ -155,7 +155,7 @@ class CourseChatsController extends State<CourseChats>
sessionsMap[activity]!.add(
ExtendedSpaceRoomsChunk(
chunk: chunk,
assignedRoles: users,
assignedRoles: users.values.toList(),
),
);
}

View file

@ -41,6 +41,38 @@ 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
@ -62,17 +94,13 @@ mixin ActivitySummariesProvider<T extends StatefulWidget> on State<T> {
for (final entry in sessions.entries) {
final session = entry.value;
final roomId = entry.key;
final roles = session.activityRoles.roles.values;
if (roles.isNotEmpty &&
(roles.any((r) => r.isArchived) ||
roles.every((r) => r.isFinished))) {
if (isActivityFinished(roomId)) {
statuses[ActivitySummaryStatus.completed]![roomId] = session;
} else if (session.activityRoles.roles.length <
session.activityPlan.req.numberOfParticipants) {
statuses[ActivitySummaryStatus.notStarted]![roomId] = session;
} else {
} else if (isActivityStarted(roomId)) {
statuses[ActivitySummaryStatus.inProgress]![roomId] = session;
} else {
statuses[ActivitySummaryStatus.notStarted]![roomId] = session;
}
}
@ -91,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);
}
}

View file

@ -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"