Watch
1
0
Fork
You've already forked nexus
0
forked from Nexus/nexus

Added a context menu entry for pinning/unpinning

; We can now also send the state event via the setState call.
This commit is contained in:
istalri 2026-07-12 00:18:03 +02:00
commit 135be17555
6 changed files with 94 additions and 13 deletions

View file

@ -94,9 +94,9 @@ A simple and user-friendly Matrix client made with Flutter and a Gomuks backend.
- [x] Editing - [x] Editing
- [x] Deleting - [x] Deleting
- [x] Reactions - [x] Reactions
- [ ] Pins - [x] Pins
- [ ] Displaying - [x] Displaying
- [ ] Creating - [x] Creating
- [ ] Threads - [ ] Threads
- [x] Profile popouts - [x] Profile popouts
- [x] Working actions - [x] Working actions

View file

@ -28,6 +28,7 @@ import "package:nexus/models/requests/report_request.dart";
import "package:nexus/models/requests/send_event_request.dart"; import "package:nexus/models/requests/send_event_request.dart";
import "package:nexus/models/requests/send_message_request.dart"; import "package:nexus/models/requests/send_message_request.dart";
import "package:nexus/models/requests/set_membership_request.dart"; import "package:nexus/models/requests/set_membership_request.dart";
import "package:nexus/models/requests/set_state_request.dart";
import "package:nexus/models/room.dart"; import "package:nexus/models/room.dart";
import "package:nexus/models/sync_data.dart"; import "package:nexus/models/sync_data.dart";
import "package:nexus/src/third_party/gomuks.g.dart"; import "package:nexus/src/third_party/gomuks.g.dart";
@ -172,6 +173,9 @@ class ClientController extends AsyncNotifier<int> {
Future<Event> sendEvent(SendEventRequest request) async => Future<Event> sendEvent(SendEventRequest request) async =>
Event.fromJson(await _sendCommand("send_event", request.toJson())); Event.fromJson(await _sendCommand("send_event", request.toJson()));
Future<Event> setState(SetStateRequest request) async =>
Event.fromJson(await _sendCommand("set_state", request.toJson()));
Future<String?> verify(String recoveryKey) async { Future<String?> verify(String recoveryKey) async {
try { try {
await _sendCommand("verify", {"recovery_key": recoveryKey}); await _sendCommand("verify", {"recovery_key": recoveryKey});

View file

@ -41,22 +41,49 @@ class PinnedEventsController extends AsyncNotifier<IList<Event>> {
return .new(); return .new();
} }
Future<void> togglePin(Event event) async { Future<void> addPin(Event event) async {
final client = ref.read(ClientController.provider.notifier); final client = ref.read(ClientController.provider.notifier);
final current = await future; final current = await future;
final updatedPinnedIds = current.any((e) => e.eventId == event.eventId) if (current.any((ev) => ev.eventId == event.eventId)) {
? current return;
.where((e) => e.eventId != event.eventId) }
.map((e) => e.eventId)
.toList()
: [...current.map((e) => e.eventId), event.eventId];
await client.sendEvent( final updatedPinnedIds = [
...current.map((ev) => ev.eventId),
event.eventId,
];
await client.setState(
.new( .new(
roomId: roomId, roomId: roomId,
type: EventType.pinnedEvents.type, type: EventType.pinnedEvents.type,
stateKey: "", stateKey:
"", //The empty string stands for m.room.pinned_events according to matrix spec.
content: {"pinned": updatedPinnedIds},
),
);
}
Future<void> removePin(Event event) async {
final client = ref.read(ClientController.provider.notifier);
final current = await future;
if (!current.any((ev) => ev.eventId == event.eventId)) {
return;
}
final updatedPinnedIds = [
...current
.where((ev) => ev.eventId != event.eventId)
.map((ev) => ev.eventId),
];
await client.setState(
.new(
roomId: roomId,
type: EventType.pinnedEvents.type,
stateKey: "", //The empty string stands for m.room.pinned_events according to matrix spec.
content: {"pinned": updatedPinnedIds}, content: {"pinned": updatedPinnedIds},
), ),
); );

View file

@ -8,7 +8,6 @@ abstract class SendEventRequest with _$SendEventRequest {
required String roomId, required String roomId,
required String type, required String type,
required Map<String, dynamic> content, required Map<String, dynamic> content,
@Default(null) String? stateKey,
@Default(false) bool synchronous, @Default(false) bool synchronous,
@Default(false) bool disableEncryption, @Default(false) bool disableEncryption,
}) = _SendEventRequest; }) = _SendEventRequest;

View file

@ -0,0 +1,17 @@
import "package:freezed_annotation/freezed_annotation.dart";
part "set_state_request.freezed.dart";
part "set_state_request.g.dart";
@freezed
abstract class SetStateRequest with _$SetStateRequest {
const factory SetStateRequest({
required String roomId,
required String type,
required String stateKey,
required Map<String, dynamic> content,
@Default(0) int delayMs,
}) = _SetStateRequest;
factory SetStateRequest.fromJson(Map<String, Object?> json) =>
_$SetStateRequestFromJson(json);
}

View file

@ -7,6 +7,7 @@ import "package:measure_size/measure_size.dart";
import "package:nexus/controllers/account_data_controller.dart"; import "package:nexus/controllers/account_data_controller.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/pinned_events_controller.dart";
import "package:nexus/controllers/power_level_controller.dart"; import "package:nexus/controllers/power_level_controller.dart";
import "package:nexus/controllers/rooms_controller.dart"; import "package:nexus/controllers/rooms_controller.dart";
import "package:nexus/controllers/room_chat_controller.dart"; import "package:nexus/controllers/room_chat_controller.dart";
@ -249,6 +250,39 @@ class RoomChat extends HookConsumerWidget {
}, },
child: ListTile(leading: Icon(Icons.edit), title: Text("Edit")), child: ListTile(leading: Icon(Icons.edit), title: Text("Edit")),
), ),
if (ref.watch(
PowerLevelController.provider(
PowerLevelConfig(eventType: .pinnedEvents, roomId: roomId),
),
))
() {
final isPinned = ref
.watch(PinnedEventsController.provider(roomId))
.maybeWhen(
data: (pinnedEvents) =>
pinnedEvents.any((ev) => ev.eventId == event.eventId),
orElse: () => false,
);
return PopupMenuItem(
onTap: () async {
final notifier = ref.read(
PinnedEventsController.provider(roomId).notifier,
);
if (isPinned) {
await notifier.removePin(event);
} else {
await notifier.addPin(event);
}
},
child: ListTile(
leading: Icon(
isPinned ? Icons.push_pin_outlined : Icons.push_pin,
),
title: Text(isPinned ? "Unpin Event" : "Pin Event"),
),
);
}(),
PopupMenuItem( PopupMenuItem(
onTap: () async { onTap: () async {
final room = ref.watch( final room = ref.watch(