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,16 +132,10 @@ class UserPopover extends ConsumerWidget {
runSpacing: 8, runSpacing: 8,
children: [ children: [
FilledButton.icon(onPressed: null, label: Text("Message")), FilledButton.icon(onPressed: null, label: Text("Message")),
if (member.status == MembershipStatus.join ||
member.status == MembershipStatus.invite)
FilledButton.icon( FilledButton.icon(
onPressed: () => client onPressed: () => showMembershipDialog(MembershipAction.kick),
.setMembership(
SetMembershipRequest(
userId: member.userId,
roomId: roomId,
action: MembershipAction.kick,
),
)
.onError(showError),
label: Text("Kick"), label: Text("Kick"),
style: ButtonStyle( style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll( backgroundColor: WidgetStatePropertyAll(
@ -100,17 +147,11 @@ class UserPopover extends ConsumerWidget {
), ),
), ),
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () => client onPressed: () => showMembershipDialog(
.setMembership( member.status == MembershipStatus.ban
SetMembershipRequest(
userId: member.userId,
roomId: roomId,
action: member.status == MembershipStatus.ban
? MembershipAction.unban ? MembershipAction.unban
: MembershipAction.ban, : MembershipAction.ban,
), ),
)
.onError(showError),
label: Text( label: Text(
member.status == MembershipStatus.ban ? "Unban" : "Ban", member.status == MembershipStatus.ban ? "Unban" : "Ban",
), ),