Support for pinned messages #49
6 changed files with 94 additions and 13 deletions
Added a context menu entry for pinning/unpinning
; We can now also send the state event via the setState call.
commit
135be17555
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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});
|
||||||
|
|
|
||||||
|
|
@ -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},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
istalri marked this conversation as resolved
Outdated
|
|||||||
|
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},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
17
lib/models/requests/set_state_request.dart
Normal file
17
lib/models/requests/set_state_request.dart
Normal 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);
|
||||||
|
}
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue
I think I'd prefer a
removePinandaddPin, separate methods. And of course this needs to be called from the context menu. The context menu should have an option of "Pin" if the event is not pinned (not present in pinned events controller), and you have permission to pin, or "Unpin" if the event IS pinned, and you have permission to pin.Yeah, makes sense, seperation of concern is a useful standard. This would make it cleaner.