fix: bring back lazy loading of members, call requestParticipants where full participant list is needed (#3815)
This commit is contained in:
parent
f632f1fbae
commit
4b3d58e30b
21 changed files with 606 additions and 1038 deletions
|
|
@ -645,12 +645,6 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
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();
|
||||
|
|
|
|||
|
|
@ -53,18 +53,6 @@ class ChatDetailsController extends State<ChatDetails> {
|
|||
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<ChatDetailsController> addConversationBotKey =
|
||||
GlobalKey<ChatDetailsController>();
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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!;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -120,7 +120,9 @@ class SpaceDetailsContentState extends State<SpaceDetailsContent> {
|
|||
? '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<SpaceDetailsContent> {
|
|||
: 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -59,13 +59,6 @@ class CourseChatsController extends State<CourseChats> {
|
|||
|
||||
@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,
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -81,9 +81,14 @@ extension CoursePlanRoomExtension on Room {
|
|||
int ownCurrentTopicIndex(CoursePlanModel course) =>
|
||||
currentTopicIndex(client.userID!, course);
|
||||
|
||||
Map<String, List<User>> topicsToUsers(CoursePlanModel course) {
|
||||
Future<Map<String, List<User>>> topicsToUsers(CoursePlanModel course) async {
|
||||
final Map<String, List<User>> 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);
|
||||
|
|
|
|||
|
|
@ -45,8 +45,12 @@ class AnalyticsRequestIndicatorState extends State<AnalyticsRequestIndicator> {
|
|||
Future<void> _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<AnalyticsRequestIndicator> {
|
|||
|
||||
@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),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -216,7 +216,12 @@ class SpaceAnalyticsState extends State<SpaceAnalytics> {
|
|||
}
|
||||
|
||||
Future<void> _initialize() async {
|
||||
await room?.requestParticipants();
|
||||
await room?.requestParticipants(
|
||||
[Membership.join, Membership.invite, Membership.knock],
|
||||
false,
|
||||
true,
|
||||
);
|
||||
|
||||
final List<Future> futures = [
|
||||
GetStorage.init('analytics_request_storage'),
|
||||
_loadProfiles(),
|
||||
|
|
|
|||
|
|
@ -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<LoadParticipantsUtil> createState() => LoadParticipantsUtilState();
|
||||
State<LoadParticipantsBuilder> createState() =>
|
||||
LoadParticipantsBuilderState();
|
||||
}
|
||||
|
||||
class LoadParticipantsUtilState extends State<LoadParticipantsUtil> {
|
||||
class LoadParticipantsBuilderState extends State<LoadParticipantsBuilder> {
|
||||
bool loading = true;
|
||||
String? error;
|
||||
|
||||
final Map<String, AnalyticsProfileModel> _levelsCache = {};
|
||||
|
||||
List<User> get participants => widget.space.getParticipants();
|
||||
List<User> get participants => widget.room.getParticipants();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -37,9 +43,9 @@ class LoadParticipantsUtilState extends State<LoadParticipantsUtil> {
|
|||
}
|
||||
|
||||
@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<LoadParticipantsUtil> {
|
|||
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<LoadParticipantsUtil> {
|
|||
}
|
||||
}
|
||||
|
||||
List<User> sortedParticipants() {
|
||||
List<User> get sortedParticipants {
|
||||
participants.sort((a, b) {
|
||||
if (a.id == BotName.byEnvironment) {
|
||||
return 1;
|
||||
|
|
@ -111,7 +122,7 @@ class LoadParticipantsUtilState extends State<LoadParticipantsUtil> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return widget.builder(this);
|
||||
return widget.builder(context, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -429,480 +429,3 @@ class DownloadAnalyticsDialogState extends State<DownloadAnalyticsDialog> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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<DownloadAnalyticsDialog> {
|
||||
// bool _initialized = false;
|
||||
// bool _downloaded = false;
|
||||
// bool _downloading = false;
|
||||
|
||||
// bool get _loading => _downloading || !_initialized;
|
||||
|
||||
// Object? _error;
|
||||
|
||||
// Map<String, int> _downloadStatuses = {};
|
||||
|
||||
// @override
|
||||
// void initState() {
|
||||
// super.initState();
|
||||
// _initialize();
|
||||
// }
|
||||
|
||||
// Future<void> _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<User> 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<void> _runDownload() async {
|
||||
// try {
|
||||
// if (!mounted || userL2 == null) return;
|
||||
// setState(() {
|
||||
// _error = null;
|
||||
// _downloading = true;
|
||||
// });
|
||||
|
||||
// final List<SpaceAnalyticsSummaryModel> summaries = [];
|
||||
// await for (final batch
|
||||
// in widget.space.getNextAnalyticsRoomBatch(userL2!)) {
|
||||
// if (batch.isEmpty) continue;
|
||||
// final List<SpaceAnalyticsSummaryModel?> batchSummaries =
|
||||
// await Future.wait(
|
||||
// batch.map((r) => _getAnalyticsModel(r)),
|
||||
// );
|
||||
// summaries
|
||||
// .addAll(batchSummaries.whereType<SpaceAnalyticsSummaryModel>());
|
||||
// }
|
||||
|
||||
// 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<void> _downloadSpaceAnalytics(
|
||||
// List<SpaceAnalyticsSummaryModel> 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<SpaceAnalyticsSummaryModel?> _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<OneConstructUse> 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<CellValue> _formatExcelRow(
|
||||
// SpaceAnalyticsSummaryModel summary,
|
||||
// ) {
|
||||
// final List<CellValue> 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<String>) {
|
||||
// row.add(TextCellValue(value.join(", ")));
|
||||
// }
|
||||
// }
|
||||
// return row;
|
||||
// }
|
||||
|
||||
// List<int> _getExcelFileContent(
|
||||
// List<SpaceAnalyticsSummaryModel> 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<SpaceAnalyticsSummaryModel> summaries,
|
||||
// ) {
|
||||
// final List<List<dynamic>> 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<String> ? 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<DownloadType>(
|
||||
// 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(),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -35,7 +35,12 @@ class KnockingUsersIndicatorState extends State<KnockingUsersIndicator> {
|
|||
.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<KnockingUsersIndicator> {
|
|||
|
||||
@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",
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ abstract class ClientManager {
|
|||
// #Pangea
|
||||
syncFilter: Filter(
|
||||
room: RoomFilter(
|
||||
state: StateFilter(lazyLoadMembers: true),
|
||||
timeline: StateFilter(
|
||||
notTypes: [
|
||||
PangeaEventTypes.construct,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue