small refactors

This commit is contained in:
Henry Hiles 2026-09-25 13:01:32 -04:00
commit 24aa0cae1d
Signed by: Henry-Hiles
SSH key fingerprint: SHA256:VKQUdS31Q90KvX7EkKMHMBpUspcmItAh86a+v7PGiIs
5 changed files with 69 additions and 48 deletions

View file

@ -17,7 +17,7 @@ import "package:nexus/models/requests/send_message.dart";
import "package:nexus/models/room.dart"; import "package:nexus/models/room.dart";
import "package:nexus/models/room_chat.dart"; import "package:nexus/models/room_chat.dart";
class RoomChatController(final (String roomId, String? withEventId) info) class RoomChatController(final (String roomId, String? contextualEvent) info)
extends AsyncNotifier<RoomChat?> { extends AsyncNotifier<RoomChat?> {
@override @override
Future<RoomChat?> build() async { Future<RoomChat?> build() async {
@ -34,31 +34,47 @@ class RoomChatController(final (String roomId, String? withEventId) info)
await ref.read(RoomsController.provider.notifier).addState(roomId, state); await ref.read(RoomsController.provider.notifier).addState(roomId, state);
} }
return .new( if (info.$2 == null ||
events: room.timeline room.events.values.map((e) => e.eventId).contains(info.$2)) {
.toEntryIList(compare: (a, b) => (a?.key ?? 0).compareTo(b?.key ?? 0)) return .new(
.map((element) => element.value) events: room.timeline
.toIList() .toEntryIList(
.addAll(room.clientSticky) compare: (a, b) => (a?.key ?? 0).compareTo(b?.key ?? 0),
.map((entry) { )
final foundEvent = entry == null ? null : room.events[entry]; .map((element) => element.value)
.toIList()
.addAll(room.clientSticky)
.map((entry) {
final foundEvent = entry == null ? null : room.events[entry];
final editedEvent = final editedEvent =
foundEvent == null || foundEvent.lastEditRowId == 0 foundEvent == null || foundEvent.lastEditRowId == 0
? null ? null
: room.events[foundEvent.lastEditRowId]; : room.events[foundEvent.lastEditRowId];
return editedEvent == null return editedEvent == null
? foundEvent ? foundEvent
: foundEvent?.copyWith( : foundEvent?.copyWith(
content: editedEvent.content, content: editedEvent.content,
localContent: editedEvent.localContent, localContent: editedEvent.localContent,
); );
}) })
.nonNulls .nonNulls
.toIList(), .toIList(),
hasMore: room.hasMore, hasMore: room.hasMore,
); );
}
{
final context = await client.getEventContext(
.new(roomId: roomId, eventId: info.$2!, limit: 20),
);
return .new(
events: context.events,
hasMore: true,
historicalData: .new(start: context.start, end: context.end),
);
}
} }
Future<void> deleteMessage(Event event, {String? reason}) => ref Future<void> deleteMessage(Event event, {String? reason}) => ref

View file

@ -92,19 +92,14 @@ final class ChatScroll({
(item) => item.eventId == itemId, (item) => item.eventId == itemId,
); );
double? offset;
if (historyIndex != -1) { if (historyIndex != -1) {
// TODO: Replace SuperSliverView because of the bug that requires this: #94 // TODO: Replace SuperSliverView because of the bug that requires this: #94
// ignore: invalid_use_of_visible_for_testing_member // ignore: invalid_use_of_visible_for_testing_member
final offset = historyListController.value.getOffsetToReveal( offset = historyListController.value.getOffsetToReveal(
historyIndex, historyIndex,
0.5, 0.5,
); );
await scrollController.animateTo(
offset,
duration: const Duration(milliseconds: 700),
curve: Curves.easeInOut,
);
} else { } else {
final liveIndex = split.live.indexWhere( final liveIndex = split.live.indexWhere(
(item) => item.eventId == itemId, (item) => item.eventId == itemId,
@ -112,18 +107,21 @@ final class ChatScroll({
if (liveIndex != -1) { if (liveIndex != -1) {
// ignore: invalid_use_of_visible_for_testing_member // ignore: invalid_use_of_visible_for_testing_member
final offset = liveListController.value.getOffsetToReveal( offset = liveListController.value.getOffsetToReveal(liveIndex, 0.5);
liveIndex,
0.5,
);
await scrollController.animateTo(
offset,
duration: const Duration(milliseconds: 700),
curve: Curves.easeInOut,
);
} }
} }
if (offset == null) {
// not in current timeline
}
if (offset != null) {
await scrollController.animateTo(
offset,
duration: const Duration(milliseconds: 700),
curve: Curves.easeInOut,
);
}
} }
return .new( return .new(

View file

@ -12,17 +12,26 @@ part "room.g.dart";
class const Room({ class const Room({
@JsonKey(name: "meta") final RoomMetadata? metadata, @JsonKey(name: "meta") final RoomMetadata? metadata,
/// [timeline] is an IMap of timelineRowId to eventRowId
@JsonKey(fromJson: Room.timelineTupleJsonToIMap) @JsonKey(fromJson: Room.timelineTupleJsonToIMap)
final IMap<int, int?> timeline = const IMap.empty(), final IMap<int, int?> timeline = const IMap.empty(),
/// [clientSticky] is an ISet of eventRowId
@JsonKey(includeFromJson: false, includeToJson: false)
final ISet<int> clientSticky = const ISet.empty(), final ISet<int> clientSticky = const ISet.empty(),
/// [events] is an IMap of eventRowId to event
@JsonKey(fromJson: Room.eventsJsonToIMap) @JsonKey(fromJson: Room.eventsJsonToIMap)
final IMap<int, Event> events = const IMap.empty(), final IMap<int, Event> events = const IMap.empty(),
final bool reset = false, final bool reset = false,
@JsonKey(includeFromJson: false, includeToJson: false)
final bool hasFetchedState = false, final bool hasFetchedState = false,
@JsonKey(includeFromJson: false, includeToJson: false)
final bool hasFetchedMembers = false, final bool hasFetchedMembers = false,
final IMap<String, IMap<String, int>> state = const IMap.empty(), final IMap<String, IMap<String, int>> state = const IMap.empty(),
final IMap<String, IList<ReadReceipt>> receipts = const IMap.empty(), final IMap<String, IList<ReadReceipt>> receipts = const IMap.empty(),
@ -32,9 +41,6 @@ class const Room({
// IMap<String, AccountData> accountData, // IMap<String, AccountData> accountData,
// IList<Notification> notifications, // IList<Notification> notifications,
}) with _$Room { }) with _$Room {
/// [timeline] is an IMap of timelineRowId to eventRowId
/// [events] is an IMap of eventRowId to event
/// [clientSticky] is an ISet of eventRowId
static IMap<int, int?> timelineTupleJsonToIMap(List<dynamic> json) => static IMap<int, int?> timelineTupleJsonToIMap(List<dynamic> json) =>
IMap.fromEntries( IMap.fromEntries(
json.map( json.map(

View file

@ -21,8 +21,8 @@ class const RoomChat({
@Freezed(toJson: false, fromJson: false) @Freezed(toJson: false, fromJson: false)
@JsonSerializable() @JsonSerializable()
class const HistoricalData({ class const HistoricalData({
required final String end,
required final String start, required final String start,
required final String end,
}) with _$HistoricalData { }) with _$HistoricalData {
Map<String, Object?> toJson() => _$HistoricalDataToJson(this); Map<String, Object?> toJson() => _$HistoricalDataToJson(this);

View file

@ -31,6 +31,7 @@ final class const RoomChat({
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final relatedEvent = useState<Event?>(null); final relatedEvent = useState<Event?>(null);
final relationType = useState(RelationType.reply); final relationType = useState(RelationType.reply);
final contextualEvent = useState<String?>(initialHighlightedEvent);
final highlightedEvent = useState<String?>(initialHighlightedEvent); final highlightedEvent = useState<String?>(initialHighlightedEvent);
final composerSize = useState<double>(64); final composerSize = useState<double>(64);
@ -62,7 +63,7 @@ final class const RoomChat({
final controllerProvider = RoomChatController.provider(( final controllerProvider = RoomChatController.provider((
roomId, roomId,
initialHighlightedEvent, contextualEvent.value,
)); ));
final notifier = ref.watch(controllerProvider.notifier); final notifier = ref.watch(controllerProvider.notifier);