Add dialog for kick/ban

This commit is contained in:
Henry Hiles 2026-04-04 12:28:49 -04:00
commit c3ca1e3491
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
3 changed files with 75 additions and 29 deletions

View file

@ -0,0 +1,4 @@
extension Capitalized on String {
String get capitalized =>
"${this[0].toUpperCase()}${substring(1).toLowerCase()}";
}

View file

@ -8,6 +8,7 @@ abstract class SetMembershipRequest with _$SetMembershipRequest {
required String userId, required String userId,
required String roomId, required String roomId,
String? reason,
@JsonKey(name: "action") required MembershipAction action, @JsonKey(name: "action") required MembershipAction action,
@Default(false) @JsonKey(name: "msc4293_redact_events") bool redact, @Default(false) @JsonKey(name: "msc4293_redact_events") bool redact,
}) = _SetMembershipRequest; }) = _SetMembershipRequest;

View file

@ -1,16 +1,19 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_hooks/flutter_hooks.dart";
import "package:flutter_riverpod/flutter_riverpod.dart"; import "package:flutter_riverpod/flutter_riverpod.dart";
import "package:nexus/controllers/client_controller.dart"; import "package:nexus/controllers/client_controller.dart";
import "package:nexus/controllers/client_state_controller.dart"; import "package:nexus/controllers/client_state_controller.dart";
import "package:nexus/controllers/profile_controller.dart"; import "package:nexus/controllers/profile_controller.dart";
import "package:nexus/controllers/selected_room_controller.dart"; import "package:nexus/controllers/selected_room_controller.dart";
import "package:nexus/helpers/extensions/better_when.dart"; import "package:nexus/helpers/extensions/better_when.dart";
import "package:nexus/helpers/extensions/capitalized.dart";
import "package:nexus/models/membership.dart"; import "package:nexus/models/membership.dart";
import "package:nexus/models/membership_status.dart"; import "package:nexus/models/membership_status.dart";
import "package:nexus/models/requests/set_membership_request.dart"; import "package:nexus/models/requests/set_membership_request.dart";
import "package:nexus/widgets/avatar_or_hash.dart"; import "package:nexus/widgets/avatar_or_hash.dart";
import "package:nexus/main.dart"; import "package:nexus/main.dart";
import "package:nexus/widgets/chat_page/expandable_image.dart"; import "package:nexus/widgets/chat_page/expandable_image.dart";
import "package:nexus/widgets/form_text_input.dart";
class UserPopover extends ConsumerWidget { class UserPopover extends ConsumerWidget {
final Membership member; final Membership member;
@ -25,6 +28,56 @@ class UserPopover extends ConsumerWidget {
SelectedRoomController.provider.select((room) => room?.metadata?.id), SelectedRoomController.provider.select((room) => room?.metadata?.id),
); );
void showMembershipDialog(MembershipAction action) => showDialog(
context: context,
builder: (context) => HookBuilder(
builder: (context) {
final actionReasonController = useTextEditingController();
return AlertDialog(
title: Text("${action.name.capitalized} ${member.userId}"),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Are you sure you want to ${action.name} ${member.userId}?",
),
SizedBox(height: 12),
FormTextInput(
required: false,
capitalize: true,
controller: actionReasonController,
title: "Reason for ${action.name} (optional)",
),
],
),
actions: [
TextButton(
onPressed: Navigator.of(context).pop,
child: Text("Cancel"),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
client
.setMembership(
SetMembershipRequest(
userId: member.userId,
roomId: roomId!,
action: action,
reason: actionReasonController.text,
),
)
.onError(showError);
},
child: Text(action.name.capitalized),
),
],
);
},
),
);
return Column( return Column(
spacing: 16, spacing: 16,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@ -79,38 +132,26 @@ class UserPopover extends ConsumerWidget {
runSpacing: 8, runSpacing: 8,
children: [ children: [
FilledButton.icon(onPressed: null, label: Text("Message")), FilledButton.icon(onPressed: null, label: Text("Message")),
FilledButton.icon( if (member.status == MembershipStatus.join ||
onPressed: () => client member.status == MembershipStatus.invite)
.setMembership( FilledButton.icon(
SetMembershipRequest( onPressed: () => showMembershipDialog(MembershipAction.kick),
userId: member.userId, label: Text("Kick"),
roomId: roomId, style: ButtonStyle(
action: MembershipAction.kick, backgroundColor: WidgetStatePropertyAll(
), theme.colorScheme.error,
) ),
.onError(showError), foregroundColor: WidgetStatePropertyAll(
label: Text("Kick"), theme.colorScheme.onError,
style: ButtonStyle( ),
backgroundColor: WidgetStatePropertyAll(
theme.colorScheme.error,
),
foregroundColor: WidgetStatePropertyAll(
theme.colorScheme.onError,
), ),
), ),
),
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () => client onPressed: () => showMembershipDialog(
.setMembership( member.status == MembershipStatus.ban
SetMembershipRequest( ? MembershipAction.unban
userId: member.userId, : MembershipAction.ban,
roomId: roomId, ),
action: member.status == MembershipStatus.ban
? MembershipAction.unban
: MembershipAction.ban,
),
)
.onError(showError),
label: Text( label: Text(
member.status == MembershipStatus.ban ? "Unban" : "Ban", member.status == MembershipStatus.ban ? "Unban" : "Ban",
), ),