make user bottom sheet scrollable

This commit is contained in:
Henry Hiles 2026-10-01 18:22:02 -04:00
commit a61cac130b
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
2 changed files with 148 additions and 156 deletions

View file

@ -7,6 +7,7 @@ import "package:m3e_card_list/m3e_card_list.dart";
import "package:nexus/controllers/members_by_status.dart"; import "package:nexus/controllers/members_by_status.dart";
import "package:nexus/controllers/members_grouped.dart"; import "package:nexus/controllers/members_grouped.dart";
import "package:nexus/helpers/extensions/get_localpart.dart"; import "package:nexus/helpers/extensions/get_localpart.dart";
import "package:nexus/helpers/extensions/show_user_popover.dart";
import "package:nexus/helpers/extensions/string_to_color.dart"; import "package:nexus/helpers/extensions/string_to_color.dart";
import "package:nexus/models/content/membership.dart"; import "package:nexus/models/content/membership.dart";
import "package:nexus/models/membership_status.dart"; import "package:nexus/models/membership_status.dart";
@ -14,7 +15,6 @@ import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/widgets/divider_text.dart"; import "package:nexus/widgets/divider_text.dart";
import "package:nexus/widgets/error_dialog.dart"; import "package:nexus/widgets/error_dialog.dart";
import "package:nexus/widgets/loading.dart"; import "package:nexus/widgets/loading.dart";
import "package:nexus/widgets/user_bottom_sheet.dart";
class const MemberList(final String roomId, {super.key}) class const MemberList(final String roomId, {super.key})
extends HookConsumerWidget { extends HookConsumerWidget {
@ -140,20 +140,10 @@ class const MemberList(final String roomId, {super.key})
final member = members[index]; final member = members[index];
if (member.content if (member.content
case MembershipContent content) { case MembershipContent content) {
showModalBottomSheet( context.showUserPopover(
constraints: .loose( content,
.new( member.stateKey!,
500, roomId: roomId,
(context.size?.height ?? 1000) - 80,
),
),
isScrollControlled: true,
context: context,
builder: (context) => UserBottomSheet(
content,
member.stateKey!,
roomId: roomId,
),
); );
} }
}, },

View file

@ -79,176 +79,178 @@ final class const UserBottomSheet(
return Padding( return Padding(
padding: .all(42), padding: .all(42),
child: Column( child: SingleChildScrollView(
spacing: 4, child: Column(
mainAxisSize: .min, spacing: 4,
crossAxisAlignment: .center, mainAxisSize: .min,
children: [ crossAxisAlignment: .center,
Row( children: [
mainAxisAlignment: .end,
children: [
M3EButton(
onPressed: Navigator.of(context).pop,
child: Icon(Icons.close),
),
],
),
SizedBox(height: 18),
ExpandableImage(
member.avatarUrl == null ? null : .new(mxc: member.avatarUrl!),
child: AvatarOrHash(
member.avatarUrl,
member.displayName ?? userId.localpart,
height: 200,
),
),
SizedBox(height: 8),
SelectableText(
member.displayName ?? userId.localpart,
style: textTheme.headlineLarge,
textAlign: .center,
),
SelectableText(
userId,
textAlign: .center,
style: textTheme.titleSmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
ref
.watch(ProfileController.provider(userId))
.betterWhen(
loading: () => Text(""),
data: (profileResponse) => Column(
children: [
if (profileResponse.profile.timezone == null &&
profileResponse.profile.pronouns.isEmpty)
Text(""),
Wrap(
crossAxisAlignment: .center,
alignment: .center,
spacing: 4,
runSpacing: 4,
children: [
...profileResponse.profile.pronouns
.where(
// TODO: Check system language (l10n)
(pronoun) => pronoun.language == "en",
)
.mapIndexed(
(index, pronoun) => [
if (index != 0)
Icon(
Icons.circle,
size: 4,
color: theme.colorScheme.onSurfaceVariant,
),
Text(
pronoun.summary,
textAlign: .center,
style: textTheme.titleSmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
)
.flattened,
if (profileResponse.profile.timezone != null) ...[
if (profileResponse.profile.pronouns.isNotEmpty)
SizedBox(
height: 16,
child: VerticalDivider(
thickness: 1.5,
width: 4,
color: theme.colorScheme.onSurfaceVariant,
),
),
Text(
profileResponse.profile.timezone!,
textAlign: .center,
style: textTheme.titleSmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
],
),
],
),
),
SizedBox(height: 8),
if (userId != ref.watch(ClientStateController.provider)?.userId &&
roomId != null) ...[
Row( Row(
mainAxisAlignment: .end,
children: [ children: [
Expanded( M3EButton(
child: M3EButton.icon( onPressed: Navigator.of(context).pop,
onPressed: null, child: Icon(Icons.close),
shape: .square,
style: .tonal,
icon: Icon(Icons.message),
label: Text("Message"),
),
), ),
], ],
), ),
SizedBox(height: 18),
if (ref.watch( ExpandableImage(
PowerLevelController.provider( member.avatarUrl == null ? null : .new(mxc: member.avatarUrl!),
.membershipAction( child: AvatarOrHash(
action: .kick, member.avatarUrl,
roomId: roomId!, member.displayName ?? userId.localpart,
targetUser: userId, height: 200,
),
), ),
)) ),
Padding(
padding: .only(top: 4), SizedBox(height: 8),
child: Row(
mainAxisSize: MainAxisSize.max, SelectableText(
spacing: 8, member.displayName ?? userId.localpart,
children: [ style: textTheme.headlineLarge,
if (member.status == .join || member.status == .invite) textAlign: .center,
),
SelectableText(
userId,
textAlign: .center,
style: textTheme.titleSmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
ref
.watch(ProfileController.provider(userId))
.betterWhen(
loading: () => Text(""),
data: (profileResponse) => Column(
children: [
if (profileResponse.profile.timezone == null &&
profileResponse.profile.pronouns.isEmpty)
Text(""),
Wrap(
crossAxisAlignment: .center,
alignment: .center,
spacing: 4,
runSpacing: 4,
children: [
...profileResponse.profile.pronouns
.where(
// TODO: Check system language (l10n)
(pronoun) => pronoun.language == "en",
)
.mapIndexed(
(index, pronoun) => [
if (index != 0)
Icon(
Icons.circle,
size: 4,
color: theme.colorScheme.onSurfaceVariant,
),
Text(
pronoun.summary,
textAlign: .center,
style: textTheme.titleSmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
)
.flattened,
if (profileResponse.profile.timezone != null) ...[
if (profileResponse.profile.pronouns.isNotEmpty)
SizedBox(
height: 16,
child: VerticalDivider(
thickness: 1.5,
width: 4,
color: theme.colorScheme.onSurfaceVariant,
),
),
Text(
profileResponse.profile.timezone!,
textAlign: .center,
style: textTheme.titleSmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
],
),
],
),
),
SizedBox(height: 8),
if (userId != ref.watch(ClientStateController.provider)?.userId &&
roomId != null) ...[
Row(
children: [
Expanded(
child: M3EButton.icon(
onPressed: null,
shape: .square,
style: .tonal,
icon: Icon(Icons.message),
label: Text("Message"),
),
),
],
),
if (ref.watch(
PowerLevelController.provider(
.membershipAction(
action: .kick,
roomId: roomId!,
targetUser: userId,
),
),
))
Padding(
padding: .only(top: 4),
child: Row(
mainAxisSize: MainAxisSize.max,
spacing: 8,
children: [
if (member.status == .join || member.status == .invite)
M3EButton.icon(
onPressed: () => showMembershipDialog(.kick),
shape: .square,
icon: Icon(Icons.sports_martial_arts),
label: Text("Kick"),
decoration: .new(
backgroundColor: WidgetStatePropertyAll(
theme.colorScheme.error,
),
foregroundColor: WidgetStatePropertyAll(
theme.colorScheme.onError,
),
),
),
M3EButton.icon( M3EButton.icon(
onPressed: () => showMembershipDialog(.kick), onPressed: () => showMembershipDialog(.ban),
shape: .square, shape: .square,
icon: Icon(Icons.sports_martial_arts), icon: Icon(Icons.gavel),
label: Text("Kick"), label: Text("Ban"),
decoration: .new( decoration: .new(
backgroundColor: WidgetStatePropertyAll( backgroundColor: WidgetStatePropertyAll(
theme.colorScheme.error, theme.colorScheme.errorContainer,
), ),
foregroundColor: WidgetStatePropertyAll( foregroundColor: WidgetStatePropertyAll(
theme.colorScheme.onError, theme.colorScheme.onErrorContainer,
), ),
), ),
), ),
].map((e) => Expanded(child: e)).toList(),
M3EButton.icon( ),
onPressed: () => showMembershipDialog(.ban),
shape: .square,
icon: Icon(Icons.gavel),
label: Text("Ban"),
decoration: .new(
backgroundColor: WidgetStatePropertyAll(
theme.colorScheme.errorContainer,
),
foregroundColor: WidgetStatePropertyAll(
theme.colorScheme.onErrorContainer,
),
),
),
].map((e) => Expanded(child: e)).toList(),
), ),
), ],
], ],
], ),
), ),
); );
} }