parent
522df86976
commit
029efe7c0a
6 changed files with 225 additions and 70 deletions
22
lib/controllers/tombstone.dart
Normal file
22
lib/controllers/tombstone.dart
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/models/content/content.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
|
||||
class TombstoneController(final String roomId) extends Notifier<Event?> {
|
||||
@override
|
||||
Event? build() => ref.watch(
|
||||
RoomsController.provider.select((rooms) {
|
||||
final room = rooms[roomId];
|
||||
return switch (room?.state[EventType.tombstone.type]?[""]) {
|
||||
final rowId? => room?.events[rowId],
|
||||
null => null,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
static final provider = NotifierProvider.family
|
||||
.autoDispose<TombstoneController, Event?, String>(
|
||||
TombstoneController.new,
|
||||
);
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import "package:nexus/models/content/redaction.dart";
|
|||
import "package:nexus/models/content/server_acl.dart";
|
||||
import "package:nexus/models/content/space_child.dart";
|
||||
import "package:nexus/models/content/topic.dart";
|
||||
import "package:nexus/models/content/tombstone.dart";
|
||||
import "package:nexus/models/content/sticker.dart";
|
||||
import "package:nexus/models/content/history_visibility.dart";
|
||||
import "package:nexus/models/content/image_pack.dart";
|
||||
|
|
@ -64,7 +65,7 @@ enum EventType(
|
|||
avatar("m.room.avatar", AvatarContent.fromJson),
|
||||
topic("m.room.topic", TopicContent.fromJson),
|
||||
name("m.room.name", NameContent.fromJson),
|
||||
tombstone("m.room.tombstone", Content.fromJson),
|
||||
tombstone("m.room.tombstone", TombstoneContent.fromJson),
|
||||
reaction("m.reaction", ReactionContent.fromJson),
|
||||
pinnedEvents("m.room.pinned_events", PinnedEventsContent.fromJson),
|
||||
spaceChild("m.space.child", SpaceChildContent.fromJson),
|
||||
|
|
|
|||
17
lib/models/content/tombstone.dart
Normal file
17
lib/models/content/tombstone.dart
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
import "package:nexus/models/content/content.dart";
|
||||
part "tombstone.freezed.dart";
|
||||
part "tombstone.g.dart";
|
||||
|
||||
@Freezed(toJson: false, fromJson: false)
|
||||
@JsonSerializable()
|
||||
class const TombstoneContent({
|
||||
final String body = "",
|
||||
required final String replacementRoom,
|
||||
}) extends Content with _$TombstoneContent {
|
||||
@override
|
||||
Map<String, Object?> toJson() => _$TombstoneContentToJson(this);
|
||||
|
||||
factory TombstoneContent.fromJson(Map<String, Object?> json) =>
|
||||
_$TombstoneContentFromJson(json);
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ import "package:nexus/helpers/extensions/send_failed.dart";
|
|||
import "package:nexus/helpers/extensions/success_scheme.dart";
|
||||
import "package:nexus/helpers/hooks/chat_scroll.dart";
|
||||
import "package:nexus/helpers/hooks/tap_recognizer.dart";
|
||||
import "package:nexus/helpers/extensions/open_link_info.dart";
|
||||
import "package:nexus/main.dart";
|
||||
import "package:nexus/models/content/avatar.dart";
|
||||
import "package:nexus/models/content/canonical_alias.dart";
|
||||
import "package:nexus/models/content/content.dart";
|
||||
|
|
@ -27,6 +29,7 @@ import "package:nexus/models/content/pinned_events.dart";
|
|||
import "package:nexus/models/content/poll_end.dart";
|
||||
import "package:nexus/models/content/power_levels.dart";
|
||||
import "package:nexus/models/content/server_acl.dart";
|
||||
import "package:nexus/models/content/tombstone.dart";
|
||||
import "package:nexus/models/content/topic.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
import "package:nexus/widgets/error_dialog.dart";
|
||||
|
|
@ -140,6 +143,27 @@ class const EventRenderer(
|
|||
],
|
||||
);
|
||||
|
||||
// For rooms an upgrade links, i.e. the old and new rooms
|
||||
Widget roomLink(String label, String linkedRoomId) => InkWell(
|
||||
onTap: () => ref
|
||||
.openLinkInfo(context, (
|
||||
identifier: linkedRoomId,
|
||||
eventId: null,
|
||||
// The sender is in both rooms, so their server can be joined
|
||||
// through
|
||||
via: [event.sender.substring(event.sender.indexOf(":") + 1)].lock,
|
||||
))
|
||||
.onError(showError),
|
||||
child: Text(
|
||||
label,
|
||||
style: theme.textTheme.labelMedium?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
decoration: .underline,
|
||||
decorationColor: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final child = event.isShownAsMessage
|
||||
? MessageRenderer(
|
||||
event,
|
||||
|
|
@ -185,7 +209,25 @@ class const EventRenderer(
|
|||
Icons.interests,
|
||||
"changed the room avatar",
|
||||
),
|
||||
CreateContent() => generic(Icons.add, "created the room"),
|
||||
CreateContent(:final predecessor) => generic(
|
||||
Icons.add,
|
||||
predecessor == null
|
||||
? "created the room"
|
||||
: "created the room, replacing an older one",
|
||||
details: [
|
||||
if (predecessor case PreviousRoom(:final roomId))
|
||||
roomLink("Go to old room", roomId),
|
||||
],
|
||||
),
|
||||
TombstoneContent(:final body, :final replacementRoom) => generic(
|
||||
Icons.upgrade,
|
||||
"replaced the room",
|
||||
details: [
|
||||
if (body.isNotEmpty)
|
||||
Text(body, style: theme.textTheme.labelMedium),
|
||||
roomLink("Go to new room", replacementRoom),
|
||||
],
|
||||
),
|
||||
PollEndContent() => switch (event.relatesTo) {
|
||||
final pollId? => switch (ref
|
||||
.watch(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import "package:nexus/controllers/client_state.dart";
|
|||
import "package:nexus/controllers/member_list_opened.dart";
|
||||
import "package:nexus/controllers/power_level.dart";
|
||||
import "package:nexus/controllers/settings.dart";
|
||||
import "package:nexus/controllers/tombstone.dart";
|
||||
import "package:nexus/controllers/rooms.dart";
|
||||
import "package:nexus/controllers/room_chat.dart";
|
||||
import "package:nexus/helpers/hooks/chat_scroll.dart";
|
||||
|
|
@ -23,6 +24,7 @@ import "package:nexus/widgets/member_list.dart";
|
|||
import "package:nexus/widgets/room_appbar.dart";
|
||||
import "package:nexus/widgets/search_drawer.dart";
|
||||
import "package:nexus/widgets/sync_status_banner.dart";
|
||||
import "package:nexus/widgets/tombstone_banner.dart";
|
||||
import "package:nexus/main.dart";
|
||||
import "package:nexus/widgets/room_chat/chat_timeline.dart";
|
||||
import "package:nexus/helpers/extensions/build_event_options.dart";
|
||||
|
|
@ -200,9 +202,14 @@ final class const RoomChat({
|
|||
(settings) => settings.value?.swipeToReply ?? false,
|
||||
),
|
||||
);
|
||||
final canReply = ref.watch(
|
||||
PowerLevelController.provider(.new(eventType: .message, roomId: roomId)),
|
||||
);
|
||||
final tombstone = ref.watch(TombstoneController.provider(roomId));
|
||||
final canReply =
|
||||
tombstone == null &&
|
||||
ref.watch(
|
||||
PowerLevelController.provider(
|
||||
.new(eventType: .message, roomId: roomId),
|
||||
),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
endDrawerEnableOpenDragGesture: false,
|
||||
|
|
@ -308,71 +315,81 @@ final class const RoomChat({
|
|||
child: MeasureSize(
|
||||
onChange: (size) =>
|
||||
composerSize.value = size.height,
|
||||
child: Composer(
|
||||
roomId,
|
||||
node: composerNode,
|
||||
onSend:
|
||||
(
|
||||
text, {
|
||||
required shouldMention,
|
||||
required tags,
|
||||
}) => notifier
|
||||
.send(
|
||||
text,
|
||||
tags: tags,
|
||||
relationType: relationType.value,
|
||||
shouldMention: shouldMention,
|
||||
relatedEventRowId:
|
||||
relatedEvent.value,
|
||||
)
|
||||
.onError(showError),
|
||||
onSendSticker:
|
||||
(
|
||||
shortcode,
|
||||
image, {
|
||||
required shouldMention,
|
||||
}) => notifier
|
||||
.sendSticker(
|
||||
shortcode,
|
||||
image,
|
||||
shouldMention: shouldMention,
|
||||
repliedEventRowId:
|
||||
repliedEventRowId,
|
||||
)
|
||||
.onError(showError),
|
||||
onSendPoll:
|
||||
(poll, {required shouldMention}) =>
|
||||
notifier
|
||||
.sendPoll(
|
||||
poll,
|
||||
shouldMention: shouldMention,
|
||||
repliedEventRowId:
|
||||
repliedEventRowId,
|
||||
)
|
||||
.onError(showError),
|
||||
onSendLocation:
|
||||
(location, {required shouldMention}) =>
|
||||
notifier
|
||||
.sendLocation(
|
||||
location,
|
||||
shouldMention: shouldMention,
|
||||
repliedEventRowId:
|
||||
repliedEventRowId,
|
||||
)
|
||||
.onError(showError),
|
||||
onSendBotCommand: (draft) => notifier
|
||||
.sendBotCommand(
|
||||
draft,
|
||||
repliedEventRowId: repliedEventRowId,
|
||||
)
|
||||
.onError(showError),
|
||||
relationType: relationType.value,
|
||||
relatedEvent: relatedEvent.value,
|
||||
onDismiss: () {
|
||||
relatedEvent.value = null;
|
||||
composerNode.requestFocus();
|
||||
},
|
||||
),
|
||||
child: switch (tombstone) {
|
||||
final tombstone? => TombstoneBanner(
|
||||
tombstone,
|
||||
),
|
||||
null => Composer(
|
||||
roomId,
|
||||
node: composerNode,
|
||||
onSend:
|
||||
(
|
||||
text, {
|
||||
required shouldMention,
|
||||
required tags,
|
||||
}) => notifier
|
||||
.send(
|
||||
text,
|
||||
tags: tags,
|
||||
relationType:
|
||||
relationType.value,
|
||||
shouldMention: shouldMention,
|
||||
relatedEventRowId:
|
||||
relatedEvent.value,
|
||||
)
|
||||
.onError(showError),
|
||||
onSendSticker:
|
||||
(
|
||||
shortcode,
|
||||
image, {
|
||||
required shouldMention,
|
||||
}) => notifier
|
||||
.sendSticker(
|
||||
shortcode,
|
||||
image,
|
||||
shouldMention: shouldMention,
|
||||
repliedEventRowId:
|
||||
repliedEventRowId,
|
||||
)
|
||||
.onError(showError),
|
||||
onSendPoll:
|
||||
(poll, {required shouldMention}) =>
|
||||
notifier
|
||||
.sendPoll(
|
||||
poll,
|
||||
shouldMention:
|
||||
shouldMention,
|
||||
repliedEventRowId:
|
||||
repliedEventRowId,
|
||||
)
|
||||
.onError(showError),
|
||||
onSendLocation:
|
||||
(
|
||||
location, {
|
||||
required shouldMention,
|
||||
}) => notifier
|
||||
.sendLocation(
|
||||
location,
|
||||
shouldMention: shouldMention,
|
||||
repliedEventRowId:
|
||||
repliedEventRowId,
|
||||
)
|
||||
.onError(showError),
|
||||
onSendBotCommand: (draft) => notifier
|
||||
.sendBotCommand(
|
||||
draft,
|
||||
repliedEventRowId:
|
||||
repliedEventRowId,
|
||||
)
|
||||
.onError(showError),
|
||||
relationType: relationType.value,
|
||||
relatedEvent: relatedEvent.value,
|
||||
onDismiss: () {
|
||||
relatedEvent.value = null;
|
||||
composerNode.requestFocus();
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
56
lib/widgets/tombstone_banner.dart
Normal file
56
lib/widgets/tombstone_banner.dart
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||
import "package:material_ui/material_ui.dart";
|
||||
import "package:nexus/helpers/extensions/open_link_info.dart";
|
||||
import "package:nexus/main.dart";
|
||||
import "package:nexus/models/content/tombstone.dart";
|
||||
import "package:nexus/models/event.dart";
|
||||
|
||||
class const TombstoneBanner(final Event tombstone, {super.key})
|
||||
extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
final content = tombstone.content;
|
||||
final sender = tombstone.sender;
|
||||
|
||||
return Container(
|
||||
color: theme.colorScheme.surfaceContainerHighest,
|
||||
padding: .symmetric(horizontal: 16, vertical: 8),
|
||||
margin: .only(top: 4),
|
||||
child: Row(
|
||||
spacing: 12,
|
||||
children: [
|
||||
Icon(Icons.upgrade),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: .min,
|
||||
crossAxisAlignment: .start,
|
||||
children: [
|
||||
Text(
|
||||
"This room has been replaced and is no longer active.",
|
||||
style: .new(fontWeight: .bold),
|
||||
),
|
||||
if (content case TombstoneContent(:final body)
|
||||
when body.isNotEmpty)
|
||||
Text(body, style: theme.textTheme.bodySmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (content case TombstoneContent(:final replacementRoom))
|
||||
FilledButton.icon(
|
||||
onPressed: () => ref
|
||||
.openLinkInfo(context, (
|
||||
identifier: replacementRoom,
|
||||
eventId: null,
|
||||
via: [sender.substring(sender.indexOf(":") + 1)].lock,
|
||||
))
|
||||
.onError(showError),
|
||||
label: Text("New room"),
|
||||
icon: Icon(Icons.open_in_new),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue