diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index b8e277831..b5944b175 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -645,12 +645,6 @@ class ChatController extends State onInsert: onInsert, ); // #Pangea - // Users with assosiated events in the timeline will be loaded into the state - // in-memory cache by getTimeline, but to ensure all users (even those without - // event in the timeline) are loaded, we will request all users. This is important - // for widgets in the chat like the activity participants displays - room.requestParticipants(); - if (visibleEvents.length < 10 && timeline != null) { var prevNumEvents = timeline!.events.length; await requestHistory(); diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index 3826010ce..21375a08c 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -53,18 +53,6 @@ class ChatDetailsController extends State { String? get roomId => widget.roomId; // #Pangea - @override - void initState() { - super.initState(); - - // Widgets within the chat details page rely on a fully-loaded in-memory - // participants list, so we need to ensure that the participants are loaded - final room = Matrix.of(context).client.getRoomById(widget.roomId); - room?.requestParticipants().then((_) { - if (mounted) setState(() {}); - }); - } - final GlobalKey addConversationBotKey = GlobalKey(); diff --git a/lib/pangea/activity_sessions/activity_finished_status_message.dart b/lib/pangea/activity_sessions/activity_finished_status_message.dart index 9117d2440..69e5f6cf9 100644 --- a/lib/pangea/activity_sessions/activity_finished_status_message.dart +++ b/lib/pangea/activity_sessions/activity_finished_status_message.dart @@ -16,6 +16,7 @@ import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; import 'package:fluffychat/pangea/analytics_summary/progress_indicators_enum.dart'; import 'package:fluffychat/pangea/courses/course_plan_room_extension.dart'; import 'package:fluffychat/pangea/courses/course_repo.dart'; +import 'package:fluffychat/pangea/spaces/utils/load_participants_util.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -80,192 +81,197 @@ class ActivityFinishedStatusMessage extends StatelessWidget { @override Widget build(BuildContext context) { - if (!controller.room.showActivityChatUI || - !controller.room.activityIsFinished || - controller.room.ownRole == null) { - return const SizedBox.shrink(); - } + return LoadParticipantsBuilder( + room: controller.room, + builder: (context, participants) { + if (!controller.room.showActivityChatUI || + !controller.room.activityIsFinished || + controller.room.ownRole == null) { + return const SizedBox.shrink(); + } - final summary = controller.room.activitySummary; + final summary = controller.room.activitySummary; + final theme = Theme.of(context); - final theme = Theme.of(context); - - final user = controller.room.getParticipants().firstWhereOrNull( + final user = participants.participants.firstWhereOrNull( (u) => u.id == _highlightedRole?.userId, ); - final userSummary = - controller.room.activitySummary?.summary?.participants.firstWhereOrNull( - (p) => p.participantId == _highlightedRole?.userId, - ); + final userSummary = controller + .room.activitySummary?.summary?.participants + .firstWhereOrNull( + (p) => p.participantId == _highlightedRole?.userId, + ); - return AnimatedSize( - duration: FluffyThemes.animationDuration, - child: Center( - child: Container( - padding: const EdgeInsets.all(16.0), - constraints: const BoxConstraints( - maxWidth: FluffyThemes.columnWidth * 1.5, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - if (summary?.summary != null) ...[ - Text( - L10n.of(context).activityFinishedMessage, - style: const TextStyle(fontSize: 18.0), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: Text( - summary!.summary!.summary, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 14.0, + return AnimatedSize( + duration: FluffyThemes.animationDuration, + child: Center( + child: Container( + padding: const EdgeInsets.all(16.0), + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 1.5, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (summary?.summary != null) ...[ + Text( + L10n.of(context).activityFinishedMessage, + style: const TextStyle(fontSize: 18.0), ), - ), - ), - if (summary.analytics != null) - Row( - spacing: 8.0, - mainAxisSize: MainAxisSize.min, - children: [ - ActivityAnalyticsChip( - ConstructTypeEnum.vocab.indicator.icon, - "${summary.analytics!.uniqueConstructCount(ConstructTypeEnum.vocab)}", + Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Text( + summary!.summary!.summary, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 14.0, + ), ), - ActivityAnalyticsChip( - ConstructTypeEnum.morph.indicator.icon, - "${summary.analytics!.uniqueConstructCount(ConstructTypeEnum.morph)}", - ), - ], - ), - const SizedBox(height: 16.0), - if (_highlightedRole != null && userSummary != null) - ClipRRect( - borderRadius: BorderRadius.circular(24.0), - child: Container( - decoration: BoxDecoration( - color: theme.colorScheme.surfaceContainer, - ), - child: Column( + ), + if (summary.analytics != null) + Row( + spacing: 8.0, + mainAxisSize: MainAxisSize.min, children: [ - ActivityResultsCarousel( - userId: _highlightedRole!.userId, - selectedRole: _highlightedRole!, - user: user, - summary: userSummary, - analytics: summary.analytics, + ActivityAnalyticsChip( + ConstructTypeEnum.vocab.indicator.icon, + "${summary.analytics!.uniqueConstructCount(ConstructTypeEnum.vocab)}", ), - Wrap( - alignment: WrapAlignment.center, - children: _rolesWithSummaries.map( - (role) { - final user = controller.room - .getParticipants() - .firstWhereOrNull( + ActivityAnalyticsChip( + ConstructTypeEnum.morph.indicator.icon, + "${summary.analytics!.uniqueConstructCount(ConstructTypeEnum.morph)}", + ), + ], + ), + const SizedBox(height: 16.0), + if (_highlightedRole != null && userSummary != null) + ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: Container( + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainer, + ), + child: Column( + children: [ + ActivityResultsCarousel( + userId: _highlightedRole!.userId, + selectedRole: _highlightedRole!, + user: user, + summary: userSummary, + analytics: summary.analytics, + ), + Wrap( + alignment: WrapAlignment.center, + children: _rolesWithSummaries.map( + (role) { + final user = participants.participants + .firstWhereOrNull( (u) => u.id == role.userId, ); - return IntrinsicWidth( - child: ActivityParticipantIndicator( - availableRole: _roles[role.id]!, - avatarUrl: _roles[role.id]?.avatarUrl ?? - user?.avatarUrl?.toString(), - onTap: _highlightedRole == role - ? null - : () => controller.highlightRole(role), - assignedRole: role, - selected: _highlightedRole == role, - padding: const EdgeInsets.symmetric( - vertical: 8.0, - horizontal: 24.0, - ), - borderRadius: BorderRadius.zero, - ), - ); - }, - ).toList(), + return IntrinsicWidth( + child: ActivityParticipantIndicator( + availableRole: _roles[role.id]!, + avatarUrl: _roles[role.id]?.avatarUrl ?? + user?.avatarUrl?.toString(), + onTap: _highlightedRole == role + ? null + : () => + controller.highlightRole(role), + assignedRole: role, + selected: _highlightedRole == role, + padding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 24.0, + ), + borderRadius: BorderRadius.zero, + ), + ); + }, + ).toList(), + ), + ], ), - ], + ), ), - ), - ), - const SizedBox(height: 20.0), - ] else if (summary?.isLoading ?? false) - Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - spacing: 8.0, - children: [ - const CircularProgressIndicator.adaptive(), - Text(L10n.of(context).loadingActivitySummary), - ], - ), - ) - else if (summary?.hasError ?? false) - Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - spacing: 8.0, - children: [ - Row( - mainAxisSize: MainAxisSize.min, + const SizedBox(height: 20.0), + ] else if (summary?.isLoading ?? false) + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + spacing: 8.0, children: [ - const Icon( - Icons.school_outlined, - size: 24.0, + const CircularProgressIndicator.adaptive(), + Text(L10n.of(context).loadingActivitySummary), + ], + ), + ) + else if (summary?.hasError ?? false) + Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + spacing: 8.0, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.school_outlined, + size: 24.0, + ), + const SizedBox(width: 8), + Flexible( + child: Text( + L10n.of(context).activitySummaryError, + textAlign: TextAlign.center, + ), + ), + ], ), - const SizedBox(width: 8), - Flexible( - child: Text( - L10n.of(context).activitySummaryError, - textAlign: TextAlign.center, - ), + TextButton( + onPressed: () => controller.room.fetchSummaries(), + child: Text(L10n.of(context).requestSummaries), ), ], ), - TextButton( - onPressed: () => controller.room.fetchSummaries(), - child: Text(L10n.of(context).requestSummaries), - ), - ], - ), - ), - if (!controller.room.isHiddenActivityRoom) - ElevatedButton( - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 8.0, ), - foregroundColor: theme.colorScheme.onPrimaryContainer, - backgroundColor: theme.colorScheme.primaryContainer, - ), - onPressed: () async { - final resp = await showFutureLoadingDialog( - context: context, - future: () => _archiveToAnalytics(context), - ); + if (!controller.room.isHiddenActivityRoom) + ElevatedButton( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 8.0, + ), + foregroundColor: theme.colorScheme.onPrimaryContainer, + backgroundColor: theme.colorScheme.primaryContainer, + ), + onPressed: () async { + final resp = await showFutureLoadingDialog( + context: context, + future: () => _archiveToAnalytics(context), + ); - if (!resp.isError) { - context.go( - "/rooms/analytics?mode=activities", - ); - } - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(L10n.of(context).archiveToAnalytics), - ], - ), - ), - ], + if (!resp.isError) { + context.go( + "/rooms/analytics?mode=activities", + ); + } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(L10n.of(context).archiveToAnalytics), + ], + ), + ), + ], + ), + ), ), - ), - ), + ); + }, ); } } diff --git a/lib/pangea/activity_sessions/activity_participant_list.dart b/lib/pangea/activity_sessions/activity_participant_list.dart index 1f6c1181c..0b1d4374d 100644 --- a/lib/pangea/activity_sessions/activity_participant_list.dart +++ b/lib/pangea/activity_sessions/activity_participant_list.dart @@ -6,6 +6,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/pangea/activity_sessions/activity_participant_indicator.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/spaces/utils/load_participants_util.dart'; import 'package:fluffychat/widgets/avatar.dart'; class ActivityParticipantList extends StatelessWidget { @@ -27,89 +28,94 @@ class ActivityParticipantList extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = Theme.of(context); + return LoadParticipantsBuilder( + room: room, + builder: (context, participants) { + final theme = Theme.of(context); + final availableRoles = room.activityPlan!.roles; + final assignedRoles = room.assignedRoles ?? {}; - final availableRoles = room.activityPlan!.roles; - final assignedRoles = room.assignedRoles ?? {}; - - final remainingMembers = room.getParticipants().where( + final remainingMembers = participants.participants.where( (p) => !assignedRoles.values.any((r) => r.userId == p.id), ); - - return Column( - spacing: 12.0, - mainAxisSize: MainAxisSize.min, - children: [ - Wrap( - alignment: WrapAlignment.center, + return Column( spacing: 12.0, - runSpacing: 12.0, - children: availableRoles.values.map((availableRole) { - final assignedRole = assignedRoles[availableRole.id]; - final user = room.getParticipants().firstWhereOrNull( + mainAxisSize: MainAxisSize.min, + children: [ + Wrap( + alignment: WrapAlignment.center, + spacing: 12.0, + runSpacing: 12.0, + children: availableRoles.values.map((availableRole) { + final assignedRole = assignedRoles[availableRole.id]; + final user = participants.participants.firstWhereOrNull( (u) => u.id == assignedRole?.userId, ); - final selectable = - canSelect != null ? canSelect!(availableRole.id) : true; + final selectable = + canSelect != null ? canSelect!(availableRole.id) : true; - return ActivityParticipantIndicator( - availableRole: availableRole, - assignedRole: assignedRole, - opacity: getOpacity != null ? getOpacity!(assignedRole) : 1.0, - avatarUrl: availableRole.avatarUrl ?? user?.avatarUrl?.toString(), - onTap: onTap != null && selectable - ? () => onTap!(availableRole.id) - : null, - selected: - isSelected != null ? isSelected!(availableRole.id) : false, - ); - }).toList(), - ), - Wrap( - alignment: WrapAlignment.center, - spacing: 12.0, - runSpacing: 12.0, - children: remainingMembers.map((member) { - return Container( - decoration: BoxDecoration( - color: theme.colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(18.0), - ), - padding: const EdgeInsets.all(4.0), - child: Opacity( - opacity: 0.5, - child: Row( - spacing: 4.0, - mainAxisSize: MainAxisSize.min, - children: [ - Avatar( - size: 18.0, - mxContent: member.avatarUrl, - name: member.calcDisplayname(), - userId: member.id, - ), - ConstrainedBox( - constraints: const BoxConstraints( - maxWidth: 80.0, - ), - child: Text( - member.calcDisplayname(), - style: TextStyle( - fontSize: 12.0, - color: theme.colorScheme.onPrimaryContainer, + return ActivityParticipantIndicator( + availableRole: availableRole, + assignedRole: assignedRole, + opacity: getOpacity != null ? getOpacity!(assignedRole) : 1.0, + avatarUrl: + availableRole.avatarUrl ?? user?.avatarUrl?.toString(), + onTap: onTap != null && selectable + ? () => onTap!(availableRole.id) + : null, + selected: isSelected != null + ? isSelected!(availableRole.id) + : false, + ); + }).toList(), + ), + Wrap( + alignment: WrapAlignment.center, + spacing: 12.0, + runSpacing: 12.0, + children: remainingMembers.map((member) { + return Container( + decoration: BoxDecoration( + color: theme.colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(18.0), + ), + padding: const EdgeInsets.all(4.0), + child: Opacity( + opacity: 0.5, + child: Row( + spacing: 4.0, + mainAxisSize: MainAxisSize.min, + children: [ + Avatar( + size: 18.0, + mxContent: member.avatarUrl, + name: member.calcDisplayname(), + userId: member.id, ), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 80.0, + ), + child: Text( + member.calcDisplayname(), + style: TextStyle( + fontSize: 12.0, + color: theme.colorScheme.onPrimaryContainer, + ), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + ], ), - ], - ), - ), - ); - }).toList(), - ), - ], + ), + ); + }).toList(), + ), + ], + ); + }, ); } } diff --git a/lib/pangea/chat_settings/pages/chat_details_content.dart b/lib/pangea/chat_settings/pages/chat_details_content.dart index bc5c71580..736fa6383 100644 --- a/lib/pangea/chat_settings/pages/chat_details_content.dart +++ b/lib/pangea/chat_settings/pages/chat_details_content.dart @@ -131,7 +131,8 @@ class ChatDetailsContent extends StatelessWidget { ), label: Text( L10n.of(context).countParticipants( - room.getParticipants().length, + (room.summary.mJoinedMemberCount ?? 0) + + (room.summary.mInvitedMemberCount ?? 0), ), maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/lib/pangea/chat_settings/pages/pangea_invitation_selection.dart b/lib/pangea/chat_settings/pages/pangea_invitation_selection.dart index ddbe1a6a3..009524172 100644 --- a/lib/pangea/chat_settings/pages/pangea_invitation_selection.dart +++ b/lib/pangea/chat_settings/pages/pangea_invitation_selection.dart @@ -90,6 +90,14 @@ class PangeaInvitationSelectionController void initState() { super.initState(); + _room?.requestParticipants( + [Membership.join, Membership.invite, Membership.knock], + false, + true, + ).then((_) { + if (mounted) setState(() {}); + }); + if (widget.initialFilter != null && availableFilters.contains(widget.initialFilter)) { filter = widget.initialFilter!; diff --git a/lib/pangea/chat_settings/pages/pangea_room_details.dart b/lib/pangea/chat_settings/pages/pangea_room_details.dart index 914fefa4a..542b49d38 100644 --- a/lib/pangea/chat_settings/pages/pangea_room_details.dart +++ b/lib/pangea/chat_settings/pages/pangea_room_details.dart @@ -34,9 +34,6 @@ class PangeaRoomDetailsView extends StatelessWidget { stream: room.client.onRoomState.stream .where((update) => update.roomId == room.id), builder: (context, snapshot) { - var members = room.getParticipants().toList() - ..sort((b, a) => a.powerLevel.compareTo(b.powerLevel)); - members = members.take(10).toList(); return Scaffold( appBar: room.isSpace ? null diff --git a/lib/pangea/chat_settings/pages/room_participants_widget.dart b/lib/pangea/chat_settings/pages/room_participants_widget.dart index 84af97b7e..6ef9d0eb3 100644 --- a/lib/pangea/chat_settings/pages/room_participants_widget.dart +++ b/lib/pangea/chat_settings/pages/room_participants_widget.dart @@ -26,11 +26,11 @@ class RoomParticipantsSection extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return LoadParticipantsUtil( - space: room, - builder: (participantsLoader) { - final filteredParticipants = participantsLoader.sortedParticipants(); - + return LoadParticipantsBuilder( + room: room, + loadProfiles: true, + builder: (context, participantsLoader) { + final filteredParticipants = participantsLoader.sortedParticipants; final originalLeaders = filteredParticipants.take(3).toList(); filteredParticipants.sort((a, b) { // always sort bot to the end diff --git a/lib/pangea/chat_settings/pages/space_details_content.dart b/lib/pangea/chat_settings/pages/space_details_content.dart index 8babaf772..8557ccafd 100644 --- a/lib/pangea/chat_settings/pages/space_details_content.dart +++ b/lib/pangea/chat_settings/pages/space_details_content.dart @@ -120,7 +120,9 @@ class SpaceDetailsContentState extends State { ? 'space' : 'contacts'; } - context.go('/rooms/${widget.room.id}/details/invite?filter=$filter'); + context.go( + '/rooms/spaces/${widget.room.id}/details/invite?filter=$filter', + ); }, enabled: widget.room.canInvite && !widget.room.isDirectChat, showInMainView: false, @@ -228,38 +230,45 @@ class SpaceDetailsContentState extends State { : CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (isColumnMode) ...[ - Avatar( - mxContent: widget.room.avatar, - name: displayname, - userId: widget.room.directChatMatrixID, - size: 80.0, - borderRadius: widget.room.isSpace - ? BorderRadius.circular(24.0) - : null, - ), - const SizedBox(width: 16.0), - ], Flexible( - child: Column( - spacing: 12.0, + child: Row( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - displayname, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: isColumnMode ? 32.0 : 12.0, + if (isColumnMode) ...[ + Avatar( + mxContent: widget.room.avatar, + name: displayname, + userId: widget.room.directChatMatrixID, + size: 80.0, + borderRadius: widget.room.isSpace + ? BorderRadius.circular(24.0) + : null, + ), + const SizedBox(width: 16.0), + ], + Flexible( + child: Column( + spacing: 12.0, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + displayname, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: isColumnMode ? 32.0 : 12.0, + ), + ), + if (isColumnMode && courseController.course != null) + CourseInfoChips( + courseController.course!, + fontSize: 12.0, + iconSize: 12.0, + ), + ], ), ), - if (isColumnMode && courseController.course != null) - CourseInfoChips( - courseController.course!, - fontSize: 12.0, - iconSize: 12.0, - ), ], ), ), diff --git a/lib/pangea/course_chats/course_chats_page.dart b/lib/pangea/course_chats/course_chats_page.dart index 7f61e5b1d..eb483fc7d 100644 --- a/lib/pangea/course_chats/course_chats_page.dart +++ b/lib/pangea/course_chats/course_chats_page.dart @@ -59,13 +59,6 @@ class CourseChatsController extends State { @override void initState() { - // load full participant list into memory to ensure widgets - // that rely on full participants list work as expected - final room = widget.client.getRoomById(widget.roomId); - room?.requestParticipants().then((_) { - if (mounted) setState(() {}); - }); - loadHierarchy(reload: true); // Listen for changes to the activeSpace's hierarchy, diff --git a/lib/pangea/course_chats/course_chats_view.dart b/lib/pangea/course_chats/course_chats_view.dart index 6b44467bc..93420bdb7 100644 --- a/lib/pangea/course_chats/course_chats_view.dart +++ b/lib/pangea/course_chats/course_chats_view.dart @@ -15,6 +15,8 @@ import 'package:fluffychat/pangea/courses/course_plan_builder.dart'; import 'package:fluffychat/pangea/courses/course_plan_model.dart'; import 'package:fluffychat/pangea/courses/course_plan_room_extension.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/space_analytics/analytics_request_indicator.dart'; +import 'package:fluffychat/pangea/spaces/widgets/knocking_users_indicator.dart'; import 'package:fluffychat/utils/stream_extension.dart'; class CourseChatsView extends StatelessWidget { @@ -59,7 +61,8 @@ class CourseChatsView extends StatelessWidget { for (final joinedRoom in joinedRooms) { if (joinedRoom.isActivitySession) { - if (activityIds.contains(joinedRoom.activityPlan?.bookmarkId)) { + if (topic == null || + activityIds.contains(joinedRoom.activityPlan?.bookmarkId)) { joinedSessions.add(joinedRoom); } } else { @@ -103,7 +106,7 @@ class CourseChatsView extends StatelessWidget { joinedSessions.length + discoveredGroupChats.length + discoveredSessions.length + - 5, + 7, itemBuilder: (context, i) { // courses chats title if (i == 0) { @@ -133,6 +136,16 @@ class CourseChatsView extends StatelessWidget { } i--; + if (i == 0) { + return KnockingUsersIndicator(room: room); + } + i--; + + if (i == 0) { + return AnalyticsRequestIndicator(room: room); + } + i--; + // joined group chats if (i < joinedChats.length) { final joinedRoom = joinedChats[i]; diff --git a/lib/pangea/course_settings/course_settings.dart b/lib/pangea/course_settings/course_settings.dart index d92b6cec5..5e8a11261 100644 --- a/lib/pangea/course_settings/course_settings.dart +++ b/lib/pangea/course_settings/course_settings.dart @@ -52,173 +52,181 @@ class CourseSettings extends StatelessWidget { room.client.userID!, course, ); - final topicsToUsers = room.topicsToUsers(course); - return Column( - spacing: isColumnMode ? 40.0 : 36.0, - mainAxisSize: MainAxisSize.min, - children: course.topics.mapIndexed((index, topic) { - final unlocked = index <= currentTopicIndex; - final usersInTopic = topicsToUsers[topic.uuid] ?? []; - return AbsorbPointer( - absorbing: !unlocked, - child: Opacity( - opacity: unlocked ? 1.0 : 0.5, - child: Column( - spacing: 12.0, - mainAxisSize: MainAxisSize.min, - children: [ - LayoutBuilder( - builder: (context, constraints) { - return Row( - spacing: 8.0, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - child: Row( - spacing: 4.0, - mainAxisSize: MainAxisSize.min, - children: [ - Stack( + return FutureBuilder( + future: room.topicsToUsers(course), + builder: (context, snapshot) { + final topicsToUsers = snapshot.data ?? {}; + return Column( + spacing: isColumnMode ? 40.0 : 36.0, + mainAxisSize: MainAxisSize.min, + children: course.topics.mapIndexed((index, topic) { + final unlocked = index <= currentTopicIndex; + final usersInTopic = topicsToUsers[topic.uuid] ?? []; + return AbsorbPointer( + absorbing: !unlocked, + child: Opacity( + opacity: unlocked ? 1.0 : 0.5, + child: Column( + spacing: 12.0, + mainAxisSize: MainAxisSize.min, + children: [ + LayoutBuilder( + builder: (context, constraints) { + return Row( + spacing: 8.0, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: Row( + spacing: 4.0, + mainAxisSize: MainAxisSize.min, children: [ - ClipPath( - clipper: PinClipper(), - child: topic.imageUrl != null - ? CachedNetworkImage( - width: 54.0, - height: 54.0, - fit: BoxFit.cover, - imageUrl: topic.imageUrl!, - placeholder: (context, url) { - return const Center( - child: - CircularProgressIndicator(), - ); - }, - errorWidget: (context, url, error) { - return Container( + Stack( + children: [ + ClipPath( + clipper: PinClipper(), + child: topic.imageUrl != null + ? CachedNetworkImage( + width: 54.0, + height: 54.0, + fit: BoxFit.cover, + imageUrl: topic.imageUrl!, + placeholder: (context, url) { + return const Center( + child: + CircularProgressIndicator(), + ); + }, + errorWidget: + (context, url, error) { + return Container( + width: 54.0, + height: 54.0, + decoration: BoxDecoration( + color: theme.colorScheme + .secondary, + ), + ); + }, + ) + : Container( width: 54.0, height: 54.0, decoration: BoxDecoration( color: theme .colorScheme.secondary, ), - ); - }, - ) - : Container( - width: 54.0, - height: 54.0, - decoration: BoxDecoration( - color: - theme.colorScheme.secondary, + ), + ), + if (!unlocked) + const Positioned( + bottom: 0, + right: 0, + child: Icon(Icons.lock, size: 24.0), + ), + ], + ), + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + topic.title, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: titleFontSize, + ), + ), + CourseInfoChip( + icon: Icons.location_on, + text: topic.location, + fontSize: descFontSize, + iconSize: iconSize, + ), + if (constraints.maxWidth < 700.0) + Padding( + padding: const EdgeInsetsGeometry + .symmetric( + vertical: 4.0, + ), + child: TopicParticipantList( + room: room, + users: usersInTopic, + avatarSize: + isColumnMode ? 50.0 : 25.0, + overlap: + isColumnMode ? 20.0 : 8.0, ), ), - ), - if (!unlocked) - const Positioned( - bottom: 0, - right: 0, - child: Icon(Icons.lock, size: 24.0), + ], ), + ), ], ), - Flexible( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - topic.title, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: titleFontSize, - ), - ), - CourseInfoChip( - icon: Icons.location_on, - text: topic.location, - fontSize: descFontSize, - iconSize: iconSize, - ), - if (constraints.maxWidth < 700.0) - Padding( - padding: - const EdgeInsetsGeometry.symmetric( - vertical: 4.0, - ), - child: TopicParticipantList( - room: room, - users: usersInTopic, - avatarSize: - isColumnMode ? 50.0 : 25.0, - overlap: isColumnMode ? 20.0 : 8.0, - ), - ), - ], - ), + ), + if (constraints.maxWidth >= 700.0) + TopicParticipantList( + room: room, + users: usersInTopic, + avatarSize: isColumnMode ? 50.0 : 25.0, + overlap: isColumnMode ? 20.0 : 8.0, ), - ], - ), - ), - if (constraints.maxWidth >= 700.0) - TopicParticipantList( - room: room, - users: usersInTopic, - avatarSize: isColumnMode ? 50.0 : 25.0, - overlap: isColumnMode ? 20.0 : 8.0, - ), - ], - ); - }, - ), - if (unlocked) - SizedBox( - height: 210.0, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: topic.activities.length, - itemBuilder: (context, index) { - final activity = topic.activities[index]; - return Padding( - padding: const EdgeInsets.only(right: 24.0), - child: ActivityPlannerBuilder( - initialActivity: activity, - room: room, - builder: (activityController) { - return ActivitySuggestionCard( - controller: activityController, - onPressed: () { - showDialog( - context: context, - builder: (context) { - return ActivitySuggestionDialog( - controller: activityController, - buttonText: - L10n.of(context).launchToSpace, - ); - }, - ); - }, - width: 120.0, - height: 200.0, - fontSize: 12.0, - fontSizeSmall: 8.0, - iconSize: 8.0, - ); - }, - ), + ], ); }, ), - ), - ], - ), - ), + if (unlocked) + SizedBox( + height: 210.0, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: topic.activities.length, + itemBuilder: (context, index) { + final activity = topic.activities[index]; + return Padding( + padding: const EdgeInsets.only(right: 24.0), + child: ActivityPlannerBuilder( + initialActivity: activity, + room: room, + builder: (activityController) { + return ActivitySuggestionCard( + controller: activityController, + onPressed: () { + showDialog( + context: context, + builder: (context) { + return ActivitySuggestionDialog( + controller: activityController, + buttonText: + L10n.of(context).launchToSpace, + ); + }, + ); + }, + width: 120.0, + height: 200.0, + fontSize: 12.0, + fontSizeSmall: 8.0, + iconSize: 8.0, + ); + }, + ), + ); + }, + ), + ), + ], + ), + ), + ); + }).toList(), ); - }).toList(), + }, ); } } diff --git a/lib/pangea/course_settings/topic_participant_list.dart b/lib/pangea/course_settings/topic_participant_list.dart index 328cc31b3..389e58317 100644 --- a/lib/pangea/course_settings/topic_participant_list.dart +++ b/lib/pangea/course_settings/topic_participant_list.dart @@ -35,9 +35,10 @@ class TopicParticipantList extends StatelessWidget { SizedBox( width: maxWidth, height: avatarSize, - child: LoadParticipantsUtil( - space: room, - builder: (participantsLoader) { + child: LoadParticipantsBuilder( + room: room, + loadProfiles: true, + builder: (context, participantsLoader) { final publicProfiles = Map.fromEntries( users.map( (u) => MapEntry( diff --git a/lib/pangea/courses/course_plan_room_extension.dart b/lib/pangea/courses/course_plan_room_extension.dart index 3a02d9a58..d459f12b2 100644 --- a/lib/pangea/courses/course_plan_room_extension.dart +++ b/lib/pangea/courses/course_plan_room_extension.dart @@ -81,9 +81,14 @@ extension CoursePlanRoomExtension on Room { int ownCurrentTopicIndex(CoursePlanModel course) => currentTopicIndex(client.userID!, course); - Map> topicsToUsers(CoursePlanModel course) { + Future>> topicsToUsers(CoursePlanModel course) async { final Map> topicUserMap = {}; - final users = getParticipants(); + final users = await requestParticipants( + [Membership.join, Membership.invite, Membership.knock], + false, + true, + ); + for (final user in users) { if (user.id == BotName.byEnvironment) continue; final topicIndex = currentTopicIndex(user.id, course); diff --git a/lib/pangea/space_analytics/analytics_request_indicator.dart b/lib/pangea/space_analytics/analytics_request_indicator.dart index 317dfb92e..4a63dd23f 100644 --- a/lib/pangea/space_analytics/analytics_request_indicator.dart +++ b/lib/pangea/space_analytics/analytics_request_indicator.dart @@ -45,8 +45,12 @@ class AnalyticsRequestIndicatorState extends State { Future _fetchKnockingAdmins() async { setState(() => _knockingAdmins.clear()); - final admins = - widget.room.getParticipants().where((u) => u.powerLevel >= 100); + final admins = (await widget.room.requestParticipants( + [Membership.join, Membership.invite, Membership.knock], + false, + true, + )) + .where((u) => u.powerLevel >= 100); for (final analyticsRoom in widget.room.client.allMyAnalyticsRooms) { final knocking = @@ -96,51 +100,46 @@ class AnalyticsRequestIndicatorState extends State { @override Widget build(BuildContext context) { - return SliverList.builder( - itemCount: 1, - itemBuilder: (context, i) { - return AnimatedSize( - duration: FluffyThemes.animationDuration, - child: _knockingAdmins.isEmpty - ? const SizedBox() - : Padding( - padding: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 1, + return AnimatedSize( + duration: FluffyThemes.animationDuration, + child: _knockingAdmins.isEmpty + ? const SizedBox() + : Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 1, + ), + child: Material( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + clipBehavior: Clip.hardEdge, + child: ListTile( + minVerticalPadding: 0, + trailing: Icon( + Icons.arrow_right, + size: 20, + color: Theme.of(context).colorScheme.error, ), - child: Material( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - clipBehavior: Clip.hardEdge, - child: ListTile( - minVerticalPadding: 0, - trailing: Icon( - Icons.arrow_right, - size: 20, + title: Row( + spacing: 8.0, + children: [ + Icon( + Icons.notifications_active_outlined, color: Theme.of(context).colorScheme.error, ), - title: Row( - spacing: 8.0, - children: [ - Icon( - Icons.notifications_active_outlined, - color: Theme.of(context).colorScheme.error, - ), - Expanded( - child: Text( - L10n.of(context).adminRequestedAccess, - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - ], + Expanded( + child: Text( + L10n.of(context).adminRequestedAccess, + style: Theme.of(context).textTheme.bodyMedium, + ), ), - onTap: () => _onTap(context), - ), + ], ), + onTap: () => _onTap(context), ), - ); - }, + ), + ), ); } } diff --git a/lib/pangea/space_analytics/space_analytics.dart b/lib/pangea/space_analytics/space_analytics.dart index a54715ddf..3f242bd21 100644 --- a/lib/pangea/space_analytics/space_analytics.dart +++ b/lib/pangea/space_analytics/space_analytics.dart @@ -216,7 +216,12 @@ class SpaceAnalyticsState extends State { } Future _initialize() async { - await room?.requestParticipants(); + await room?.requestParticipants( + [Membership.join, Membership.invite, Membership.knock], + false, + true, + ); + final List futures = [ GetStorage.init('analytics_request_storage'), _loadProfiles(), diff --git a/lib/pangea/spaces/utils/load_participants_util.dart b/lib/pangea/spaces/utils/load_participants_util.dart index 4a79e7dff..1f6fd2735 100644 --- a/lib/pangea/spaces/utils/load_participants_util.dart +++ b/lib/pangea/spaces/utils/load_participants_util.dart @@ -8,27 +8,33 @@ import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/user/models/analytics_profile_model.dart'; import 'package:fluffychat/widgets/matrix.dart'; -class LoadParticipantsUtil extends StatefulWidget { - final Room space; - final Widget Function(LoadParticipantsUtilState) builder; +class LoadParticipantsBuilder extends StatefulWidget { + final Room room; + final bool loadProfiles; + final Widget Function( + BuildContext context, + LoadParticipantsBuilderState, + ) builder; - const LoadParticipantsUtil({ - required this.space, + const LoadParticipantsBuilder({ + required this.room, required this.builder, + this.loadProfiles = false, super.key, }); @override - State createState() => LoadParticipantsUtilState(); + State createState() => + LoadParticipantsBuilderState(); } -class LoadParticipantsUtilState extends State { +class LoadParticipantsBuilderState extends State { bool loading = true; String? error; final Map _levelsCache = {}; - List get participants => widget.space.getParticipants(); + List get participants => widget.room.getParticipants(); @override void initState() { @@ -37,9 +43,9 @@ class LoadParticipantsUtilState extends State { } @override - void didUpdateWidget(LoadParticipantsUtil oldWidget) { + void didUpdateWidget(LoadParticipantsBuilder oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.space != widget.space) { + if (oldWidget.room.id != widget.room.id) { _loadParticipants(); } } @@ -51,15 +57,20 @@ class LoadParticipantsUtilState extends State { error = null; }); - await widget.space.requestParticipants(); - await _cacheLevels(); + await widget.room.requestParticipants( + [Membership.join, Membership.invite, Membership.knock], + false, + true, + ); + + if (widget.loadProfiles) await _cacheLevels(); } catch (err, s) { error = err.toString(); ErrorHandler.logError( e: err, s: s, data: { - 'spaceId': widget.space.id, + 'roomId': widget.room.id, }, ); } finally { @@ -69,7 +80,7 @@ class LoadParticipantsUtilState extends State { } } - List sortedParticipants() { + List get sortedParticipants { participants.sort((a, b) { if (a.id == BotName.byEnvironment) { return 1; @@ -111,7 +122,7 @@ class LoadParticipantsUtilState extends State { @override Widget build(BuildContext context) { - return widget.builder(this); + return widget.builder(context, this); } } diff --git a/lib/pangea/spaces/widgets/download_space_analytics_dialog.dart b/lib/pangea/spaces/widgets/download_space_analytics_dialog.dart index 244a26f28..850354921 100644 --- a/lib/pangea/spaces/widgets/download_space_analytics_dialog.dart +++ b/lib/pangea/spaces/widgets/download_space_analytics_dialog.dart @@ -429,480 +429,3 @@ class DownloadAnalyticsDialogState extends State { ); } } - -// import 'package:flutter/material.dart'; - -// import 'package:csv/csv.dart'; -// import 'package:excel/excel.dart'; -// import 'package:matrix/matrix.dart'; - -// import 'package:fluffychat/config/app_config.dart'; -// import 'package:fluffychat/config/themes.dart'; -// import 'package:fluffychat/l10n/l10n.dart'; -// import 'package:fluffychat/pangea/analytics_downloads/space_analytics_summary_enum.dart'; -// import 'package:fluffychat/pangea/analytics_downloads/space_analytics_summary_model.dart'; -// import 'package:fluffychat/pangea/analytics_misc/construct_list_model.dart'; -// import 'package:fluffychat/pangea/analytics_misc/construct_use_model.dart'; -// import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart'; -// import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; -// import 'package:fluffychat/pangea/chat_settings/utils/download_file.dart'; -// import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -// import 'package:fluffychat/pangea/common/widgets/error_indicator.dart'; -// import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; -// import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart'; -// import 'package:fluffychat/widgets/matrix.dart'; - -// class DownloadAnalyticsDialog extends StatefulWidget { -// final Room space; -// const DownloadAnalyticsDialog({ -// required this.space, -// super.key, -// }); - -// @override -// DownloadAnalyticsDialogState createState() => DownloadAnalyticsDialogState(); -// } - -// class DownloadAnalyticsDialogState extends State { -// bool _initialized = false; -// bool _downloaded = false; -// bool _downloading = false; - -// bool get _loading => _downloading || !_initialized; - -// Object? _error; - -// Map _downloadStatuses = {}; - -// @override -// void initState() { -// super.initState(); -// _initialize(); -// } - -// Future _initialize() async { -// try { -// await widget.space.requestParticipants( -// [Membership.join], -// false, -// true, -// ); -// } catch (e, s) { -// ErrorHandler.logError( -// e: e, -// s: s, -// data: { -// "spaceID": widget.space.id, -// }, -// ); -// } finally { -// _downloadStatuses = Map.fromEntries( -// _usersToDownload.map((user) => MapEntry(user.id, 0)), -// ); -// if (mounted) setState(() => _initialized = true); -// } -// } - -// DownloadType _downloadType = DownloadType.csv; - -// void _setDownloadType(DownloadType type) { -// _clean(); -// if (mounted) setState(() => _downloadType = type); -// } - -// void _clean() { -// _error = null; -// _downloading = false; -// _downloaded = false; -// _downloadStatuses = Map.fromEntries( -// _usersToDownload.map((user) => MapEntry(user.id, 0)), -// ); -// } - -// List get _usersToDownload => widget.space -// .getParticipants() -// .where( -// (member) => -// member.id != BotName.byEnvironment && -// member.membership == Membership.join, -// ) -// .toList(); - -// Color _downloadStatusColor(String userID) { -// final status = _downloadStatuses[userID]; -// if (status == 1) return Colors.yellow; -// if (status == 2) return Colors.green; -// if ((status ?? 0) < 0) return Colors.red; -// return Colors.grey; -// } - -// String? get _statusText { -// if (_downloading) return L10n.of(context).downloading; -// if (_downloaded) return L10n.of(context).downloadComplete; -// return null; -// } - -// String? get userL2 => -// MatrixState.pangeaController.languageController.userL2?.langCode; - -// Future _runDownload() async { -// try { -// if (!mounted || userL2 == null) return; -// setState(() { -// _error = null; -// _downloading = true; -// }); - -// final List summaries = []; -// await for (final batch -// in widget.space.getNextAnalyticsRoomBatch(userL2!)) { -// if (batch.isEmpty) continue; -// final List batchSummaries = -// await Future.wait( -// batch.map((r) => _getAnalyticsModel(r)), -// ); -// summaries -// .addAll(batchSummaries.whereType()); -// } - -// for (final userID in _downloadStatuses.keys) { -// if (_downloadStatuses[userID] == 0) { -// _downloadStatuses[userID] = -1; -// summaries.add(SpaceAnalyticsSummaryModel.emptyModel(userID)); -// } -// } - -// await _downloadSpaceAnalytics(summaries); - -// if (mounted) { -// setState(() { -// _downloading = false; -// _downloaded = true; -// }); -// } -// } catch (e, s) { -// ErrorHandler.logError( -// e: e, -// s: s, -// data: { -// "spaceID": widget.space.id, -// }, -// ); - -// _clean(); -// _error = e; -// if (mounted) setState(() {}); -// } -// } - -// Future _downloadSpaceAnalytics( -// List summaries, -// ) async { -// final content = _downloadType == DownloadType.xlsx -// ? _getExcelFileContent(summaries) -// : _getCSVFileContent(summaries); - -// final fileName = -// "analytics_${widget.space.name}_${DateTime.now().toIso8601String()}.${_downloadType == DownloadType.xlsx ? 'xlsx' : 'csv'}"; - -// await downloadFile( -// content, -// fileName, -// DownloadType.csv, -// ); -// } - -// Future _getAnalyticsModel( -// Room analyticsRoom, -// ) async { -// final String? userID = analyticsRoom.creatorId; -// if (userID == null) return null; - -// SpaceAnalyticsSummaryModel? summary; -// try { -// _downloadStatuses[userID] = 1; -// if (mounted) setState(() {}); - -// final constructEvents = await analyticsRoom.getAnalyticsEvents( -// userId: userID, -// ); - -// if (constructEvents == null) { -// if (mounted) setState(() => _downloadStatuses[userID] = -1); -// return SpaceAnalyticsSummaryModel.emptyModel(userID); -// } - -// final List uses = []; -// for (final event in constructEvents) { -// uses.addAll(event.content.uses); -// } - -// final constructs = ConstructListModel(uses: uses); -// summary = SpaceAnalyticsSummaryModel.fromConstructListModel( -// userID, -// constructs, -// 0, -// getCopy, -// context, -// ); -// if (mounted) setState(() => _downloadStatuses[userID] = 2); -// } catch (e, s) { -// ErrorHandler.logError( -// e: e, -// s: s, -// data: { -// "spaceID": widget.space.id, -// "userID": userID, -// }, -// ); -// if (mounted) setState(() => _downloadStatuses[userID] = -2); -// } finally { -// if (userID != widget.space.client.userID) { -// try { -// await analyticsRoom.leave(); -// } catch (e, s) { -// ErrorHandler.logError( -// e: e, -// s: s, -// data: { -// "spaceID": widget.space.id, -// "userID": userID, -// }, -// ); -// } -// } -// } -// return summary; -// } - -// List _formatExcelRow( -// SpaceAnalyticsSummaryModel summary, -// ) { -// final List row = []; -// for (int i = 0; i < SpaceAnalyticsSummaryEnum.values.length; i++) { -// final key = SpaceAnalyticsSummaryEnum.values[i]; -// final value = summary.getValue(key, context); -// if (value is int) { -// row.add(IntCellValue(value)); -// } else if (value is String) { -// row.add(TextCellValue(value)); -// } else if (value is List) { -// row.add(TextCellValue(value.join(", "))); -// } -// } -// return row; -// } - -// List _getExcelFileContent( -// List summaries, -// ) { -// final excel = Excel.createExcel(); -// final sheet = excel['Sheet1']; - -// for (final key in SpaceAnalyticsSummaryEnum.values) { -// sheet -// .cell( -// CellIndex.indexByColumnRow( -// rowIndex: 0, -// columnIndex: key.index, -// ), -// ) -// .value = TextCellValue(key.header(L10n.of(context))); -// } - -// final rows = summaries.map((summary) => _formatExcelRow(summary)).toList(); - -// for (int i = 0; i < rows.length; i++) { -// final row = rows[i]; -// for (int j = 0; j < row.length; j++) { -// final cell = row[j]; -// sheet -// .cell(CellIndex.indexByColumnRow(rowIndex: i + 2, columnIndex: j)) -// .value = cell; -// } -// } -// return excel.encode() ?? []; -// } - -// String _getCSVFileContent( -// List summaries, -// ) { -// final List> rows = []; -// final headerRow = []; -// for (final key in SpaceAnalyticsSummaryEnum.values) { -// headerRow.add(key.header(L10n.of(context))); -// } -// rows.add(headerRow); - -// for (final summary in summaries) { -// final row = []; -// for (int i = 0; i < SpaceAnalyticsSummaryEnum.values.length; i++) { -// final key = SpaceAnalyticsSummaryEnum.values[i]; -// final value = summary.getValue(key, context); -// if (value == null) continue; -// value is List ? row.add(value.join(", ")) : row.add(value); -// } -// rows.add(row); -// } - -// final String fileString = const ListToCsvConverter().convert(rows); -// return fileString; -// } - -// String getCopy(ConstructUses use) { -// return getGrammarCopy( -// category: use.category, -// lemma: use.lemma, -// context: context, -// ) ?? -// use.lemma; -// } - -// @override -// Widget build(BuildContext context) { -// return Dialog( -// child: Container( -// constraints: const BoxConstraints( -// maxWidth: 400, -// ), -// padding: const EdgeInsets.symmetric(vertical: 20), -// child: Column( -// mainAxisSize: MainAxisSize.min, -// children: [ -// Text( -// L10n.of(context).fileType, -// style: TextStyle( -// fontSize: AppConfig.fontSizeFactor * AppConfig.messageFontSize, -// ), -// ), -// Padding( -// padding: const EdgeInsets.all(8.0), -// child: SegmentedButton( -// selected: {_downloadType}, -// onSelectionChanged: (c) => _setDownloadType(c.first), -// segments: [ -// ButtonSegment( -// value: DownloadType.csv, -// label: Text(L10n.of(context).commaSeparatedFile), -// ), -// ButtonSegment( -// value: DownloadType.xlsx, -// label: Text(L10n.of(context).excelFile), -// ), -// ], -// ), -// ), -// Padding( -// padding: const EdgeInsets.all(8.0), -// child: ConstrainedBox( -// constraints: const BoxConstraints( -// maxHeight: 300, -// minHeight: 0, -// ), -// child: ListView.builder( -// shrinkWrap: true, -// itemCount: _usersToDownload.length, -// itemBuilder: (context, index) { -// final user = _usersToDownload[index]; - -// String tooltip = ""; -// if (_downloadStatuses[user.id] == -1) { -// tooltip = L10n.of(context).analyticsNotAvailable; -// } else if (_downloadStatuses[user.id] == -2) { -// tooltip = L10n.of(context).failedFetchUserAnalytics; -// } - -// return Padding( -// padding: const EdgeInsets.all(4.0), -// child: AnimatedOpacity( -// duration: FluffyThemes.animationDuration, -// opacity: -// (_downloadStatuses[user.id] ?? 0) > 0 ? 1 : 0.5, -// child: Row( -// children: [ -// SizedBox( -// width: 40, -// height: 30, -// child: (_downloadStatuses[user.id] ?? 0) < 0 -// ? const Icon( -// Icons.error_outline, -// size: 16, -// ) -// : Center( -// child: AnimatedContainer( -// duration: -// FluffyThemes.animationDuration, -// height: 12, -// width: 12, -// decoration: BoxDecoration( -// color: _downloadStatusColor(user.id), -// borderRadius: -// BorderRadius.circular(100), -// ), -// ), -// ), -// ), -// Flexible( -// child: Column( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Text(user.displayName ?? user.id), -// if (tooltip.isNotEmpty) -// Text( -// tooltip, -// style: const TextStyle(fontSize: 10), -// ), -// ], -// ), -// ), -// ], -// ), -// ), -// ); -// }, -// ), -// ), -// ), -// Padding( -// padding: const EdgeInsets.fromLTRB(8.0, 16.0, 8.0, 8.0), -// child: OutlinedButton( -// onPressed: _loading || !_initialized ? null : _runDownload, -// child: _initialized && !_loading -// ? Text( -// _loading -// ? L10n.of(context).downloading -// : L10n.of(context).download, -// ) -// : const SizedBox( -// height: 10, -// width: 100, -// child: LinearProgressIndicator(), -// ), -// ), -// ), -// AnimatedSize( -// duration: FluffyThemes.animationDuration, -// child: _statusText != null -// ? Padding( -// padding: const EdgeInsets.all(8.0), -// child: Text(_statusText!), -// ) -// : const SizedBox(), -// ), -// AnimatedSize( -// duration: FluffyThemes.animationDuration, -// child: _error != null -// ? Padding( -// padding: const EdgeInsets.all(8.0), -// child: ErrorIndicator( -// message: L10n.of(context).errorDownloading, -// ), -// ) -// : const SizedBox(), -// ), -// ], -// ), -// ), -// ); -// } -// } diff --git a/lib/pangea/spaces/widgets/knocking_users_indicator.dart b/lib/pangea/spaces/widgets/knocking_users_indicator.dart index 184962460..9816f3637 100644 --- a/lib/pangea/spaces/widgets/knocking_users_indicator.dart +++ b/lib/pangea/spaces/widgets/knocking_users_indicator.dart @@ -35,7 +35,12 @@ class KnockingUsersIndicatorState extends State { .where(_isMemberUpdate) .rateLimit(const Duration(seconds: 1)) .listen((_) => _setKnockingUsers()); - _setKnockingUsers(); + + widget.room.requestParticipants( + [Membership.join, Membership.invite, Membership.knock], + false, + true, + ).then((_) => _setKnockingUsers()); } bool _isMemberUpdate(({String roomId, StrippedStateEvent state}) event) => @@ -58,55 +63,50 @@ class KnockingUsersIndicatorState extends State { @override Widget build(BuildContext context) { - return SliverList.builder( - itemCount: 1, - itemBuilder: (context, i) { - return AnimatedSize( - duration: FluffyThemes.animationDuration, - child: _knockingUsers.isEmpty || !widget.room.isRoomAdmin - ? const SizedBox() - : Padding( - padding: const EdgeInsets.symmetric( - horizontal: 4, - vertical: 1, + return AnimatedSize( + duration: FluffyThemes.animationDuration, + child: _knockingUsers.isEmpty || !widget.room.isRoomAdmin + ? const SizedBox() + : Padding( + padding: const EdgeInsets.symmetric( + horizontal: 4, + vertical: 1, + ), + child: Material( + borderRadius: BorderRadius.circular( + AppConfig.borderRadius, + ), + clipBehavior: Clip.hardEdge, + child: ListTile( + minVerticalPadding: 0, + trailing: Icon( + Icons.adaptive.arrow_forward_outlined, + size: 16, ), - child: Material( - borderRadius: BorderRadius.circular( - AppConfig.borderRadius, - ), - clipBehavior: Clip.hardEdge, - child: ListTile( - minVerticalPadding: 0, - trailing: Icon( - Icons.adaptive.arrow_forward_outlined, - size: 16, + title: Row( + spacing: 8.0, + children: [ + Icon( + Icons.notifications_outlined, + color: Theme.of(context).colorScheme.error, ), - title: Row( - spacing: 8.0, - children: [ - Icon( - Icons.notifications_outlined, - color: Theme.of(context).colorScheme.error, - ), - Expanded( - child: Text( - _knockingUsers.length == 1 - ? L10n.of(context).aUserIsKnocking - : L10n.of(context) - .usersAreKnocking(_knockingUsers.length), - style: Theme.of(context).textTheme.bodyMedium, - ), - ), - ], + Expanded( + child: Text( + _knockingUsers.length == 1 + ? L10n.of(context).aUserIsKnocking + : L10n.of(context) + .usersAreKnocking(_knockingUsers.length), + style: Theme.of(context).textTheme.bodyMedium, + ), ), - onTap: () => context.push( - "/rooms/${widget.room.id}/details/members?filter=knock", - ), - ), + ], + ), + onTap: () => context.push( + "/rooms/spaces/${widget.room.id}/details/members?filter=knock", ), ), - ); - }, + ), + ), ); } } diff --git a/lib/pangea/spaces/widgets/leaderboard_participant_list.dart b/lib/pangea/spaces/widgets/leaderboard_participant_list.dart index fe7a47e7b..89c951279 100644 --- a/lib/pangea/spaces/widgets/leaderboard_participant_list.dart +++ b/lib/pangea/spaces/widgets/leaderboard_participant_list.dart @@ -44,11 +44,11 @@ class LeaderboardParticipantListState return StreamBuilder( stream: client.onSync.stream.rateLimit(const Duration(seconds: 3)), builder: (context, snapshot) { - return LoadParticipantsUtil( - space: widget.space, - builder: (participantsLoader) { - final participants = participantsLoader - .sortedParticipants() + return LoadParticipantsBuilder( + room: widget.space, + loadProfiles: true, + builder: (context, participantsLoader) { + final participants = participantsLoader.sortedParticipants .where((p) => p.membership == Membership.join) .toList(); diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 13266ba91..5d9c6ccea 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -163,6 +163,7 @@ abstract class ClientManager { // #Pangea syncFilter: Filter( room: RoomFilter( + state: StateFilter(lazyLoadMembers: true), timeline: StateFilter( notTypes: [ PangeaEventTypes.construct,