chore: add shimmer to selectable activity roles (#4743)

This commit is contained in:
ggurdin 2025-11-21 13:12:38 -05:00 committed by GitHub
parent 35ffefbfd7
commit 15c934ae53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 70 additions and 28 deletions

View file

@ -2,7 +2,9 @@ import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:matrix/matrix.dart';
import 'package:shimmer/shimmer.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/string_color.dart';
import 'package:fluffychat/widgets/avatar.dart';
@ -17,6 +19,7 @@ class ActivityParticipantIndicator extends StatelessWidget {
final VoidCallback? onTap;
final bool selected;
final bool selectable;
final bool shimmer;
final double opacity;
final EdgeInsetsGeometry? padding;
@ -29,6 +32,7 @@ class ActivityParticipantIndicator extends StatelessWidget {
this.userId,
this.selected = false,
this.selectable = true,
this.shimmer = false,
this.onTap,
this.opacity = 1.0,
this.padding,
@ -70,35 +74,46 @@ class ActivityParticipantIndicator extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
userId != null
? user?.avatarUrl == null ||
user!.avatarUrl!.toString().startsWith("mxc")
? Avatar(
mxContent: user?.avatarUrl != null
? user!.avatarUrl!
: null,
name: userId!.localpart,
size: 60.0,
userId: userId,
)
: ClipRRect(
borderRadius: BorderRadius.circular(30),
child: CachedNetworkImage(
imageUrl: user!.avatarUrl!.toString(),
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
),
)
: CircleAvatar(
radius: 30.0,
backgroundColor:
theme.colorScheme.primaryContainer,
child: const Icon(
Icons.question_mark,
size: 30.0,
Shimmer.fromColors(
enabled: shimmer && !selected,
baseColor: shimmer && !selected
? AppConfig.gold.withAlpha(20)
: Colors.transparent,
highlightColor: shimmer && !selected
? AppConfig.gold.withAlpha(50)
: Colors.transparent,
child: userId != null
? user?.avatarUrl == null ||
user!.avatarUrl!
.toString()
.startsWith("mxc")
? Avatar(
mxContent: user?.avatarUrl != null
? user!.avatarUrl!
: null,
name: userId!.localpart,
size: 60.0,
userId: userId,
)
: ClipRRect(
borderRadius: BorderRadius.circular(30),
child: CachedNetworkImage(
imageUrl: user!.avatarUrl!.toString(),
width: 60.0,
height: 60.0,
fit: BoxFit.cover,
),
)
: CircleAvatar(
radius: 30.0,
backgroundColor:
theme.colorScheme.primaryContainer,
child: const Icon(
Icons.question_mark,
size: 30.0,
),
),
),
),
Text(
name,
style: const TextStyle(

View file

@ -20,6 +20,7 @@ class ActivityParticipantList extends StatelessWidget {
final bool Function(String)? canSelect;
final bool Function(String)? isSelected;
final bool Function(String)? isShimmering;
final double Function(ActivityRoleModel?)? getOpacity;
const ActivityParticipantList({
@ -31,6 +32,7 @@ class ActivityParticipantList extends StatelessWidget {
this.onTap,
this.canSelect,
this.isSelected,
this.isShimmering,
this.getOpacity,
});
@ -76,6 +78,10 @@ class ActivityParticipantList extends StatelessWidget {
final selectable =
canSelect != null ? canSelect!(availableRole.id) : true;
final shimmering = isShimmering != null
? isShimmering!(availableRole.id)
: false;
return ActivityParticipantIndicator(
name: availableRole.name,
userId: assignedRole?.userId,
@ -86,6 +92,7 @@ class ActivityParticipantList extends StatelessWidget {
: null,
selected: selected,
selectable: selectable,
shimmer: shimmering,
);
}).toList(),
),

View file

@ -179,6 +179,21 @@ class ActivitySessionStartController extends State<ActivitySessionStartPage>
return unassignedIds.contains(id);
}
bool isParticipantShimmering(String id) {
if (state != SessionState.notSelectedRole) {
return false;
}
final availableRoles = activity!.roles;
final assignedRoles = activityRoom?.assignedRoles ??
roomSummaries?[widget.roomId]?.activityRoles.roles ??
{};
final unassignedIds = availableRoles.keys
.where((id) => !assignedRoles.containsKey(id))
.toList();
return unassignedIds.contains(id);
}
bool isParticipantSelected(String id) {
if (state == SessionState.confirmedRole) {
return activityRoom?.ownRoleState?.id == id;

View file

@ -161,6 +161,8 @@ class ActivitySessionStartView extends StatelessWidget {
onTapParticipant: controller.selectRole,
isParticipantSelected:
controller.isParticipantSelected,
isParticipantShimmering:
controller.isParticipantShimmering,
canSelectParticipant:
controller.canSelectParticipant,
assignedRoles: controller.assignedRoles,

View file

@ -31,6 +31,7 @@ class ActivitySummary extends StatelessWidget {
final Function(String)? onTapParticipant;
final bool Function(String)? canSelectParticipant;
final bool Function(String)? isParticipantSelected;
final bool Function(String)? isParticipantShimmering;
final double Function(ActivityRoleModel?)? getParticipantOpacity;
final ValueNotifier<Set<String>>? usedVocab;
@ -45,6 +46,7 @@ class ActivitySummary extends StatelessWidget {
this.onTapParticipant,
this.canSelectParticipant,
this.isParticipantSelected,
this.isParticipantShimmering,
this.getParticipantOpacity,
this.room,
this.course,
@ -82,6 +84,7 @@ class ActivitySummary extends StatelessWidget {
onTap: onTapParticipant,
canSelect: canSelectParticipant,
isSelected: isParticipantSelected,
isShimmering: isParticipantShimmering,
getOpacity: getParticipantOpacity,
),
DecoratedBox(