refactor for RoomChatController to return a RoomChat
Will help with historical timeline support.
This commit is contained in:
parent
7d8505bdf1
commit
08e60a87df
5 changed files with 82 additions and 46 deletions
|
|
@ -15,11 +15,11 @@ import "package:nexus/models/requests/redact_event.dart";
|
||||||
import "package:nexus/models/relation_type.dart";
|
import "package:nexus/models/relation_type.dart";
|
||||||
import "package:nexus/models/requests/send_message.dart";
|
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";
|
||||||
|
|
||||||
class RoomChatController(final String roomId)
|
class RoomChatController(final String roomId) extends AsyncNotifier<RoomChat?> {
|
||||||
extends AsyncNotifier<IList<Event>?> {
|
|
||||||
@override
|
@override
|
||||||
Future<IList<Event>?> build() async {
|
Future<RoomChat?> build() async {
|
||||||
final client = ref.read(ClientController.provider.notifier);
|
final client = ref.read(ClientController.provider.notifier);
|
||||||
final room = ref.watch(
|
final room = ref.watch(
|
||||||
RoomsController.provider.select((rooms) => rooms[roomId]),
|
RoomsController.provider.select((rooms) => rooms[roomId]),
|
||||||
|
|
@ -32,7 +32,8 @@ class RoomChatController(final String roomId)
|
||||||
await ref.read(RoomsController.provider.notifier).addState(roomId, state);
|
await ref.read(RoomsController.provider.notifier).addState(roomId, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
return room.timeline
|
return .new(
|
||||||
|
events: room.timeline
|
||||||
.toEntryIList(compare: (a, b) => (a?.key ?? 0).compareTo(b?.key ?? 0))
|
.toEntryIList(compare: (a, b) => (a?.key ?? 0).compareTo(b?.key ?? 0))
|
||||||
.map((element) => element.value)
|
.map((element) => element.value)
|
||||||
.toIList()
|
.toIList()
|
||||||
|
|
@ -53,7 +54,9 @@ class RoomChatController(final String roomId)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.nonNulls
|
.nonNulls
|
||||||
.toIList();
|
.toIList(),
|
||||||
|
hasMore: room.hasMore,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteMessage(Event event, {String? reason}) => ref
|
Future<void> deleteMessage(Event event, {String? reason}) => ref
|
||||||
|
|
@ -67,7 +70,7 @@ class RoomChatController(final String roomId)
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> loadOlder() async {
|
Future<void> loadOlder() async {
|
||||||
if (state.isLoading) return;
|
if (state.isLoading || state.value?.hasMore == false) return;
|
||||||
|
|
||||||
state = .loading();
|
state = .loading();
|
||||||
|
|
||||||
|
|
@ -224,7 +227,7 @@ class RoomChatController(final String roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
static final provider = AsyncNotifierProvider.family
|
static final provider = AsyncNotifierProvider.family
|
||||||
.autoDispose<RoomChatController, IList<Event>?, String>(
|
.autoDispose<RoomChatController, RoomChat?, String>(
|
||||||
RoomChatController.new,
|
RoomChatController.new,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,23 @@ import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
import "package:hooks_riverpod/hooks_riverpod.dart";
|
import "package:hooks_riverpod/hooks_riverpod.dart";
|
||||||
import "package:material_ui/material_ui.dart";
|
import "package:material_ui/material_ui.dart";
|
||||||
|
import "package:nexus/models/event.dart";
|
||||||
|
import "package:nexus/models/room_chat.dart";
|
||||||
import "package:super_sliver_list/super_sliver_list.dart";
|
import "package:super_sliver_list/super_sliver_list.dart";
|
||||||
|
|
||||||
final class ChatScroll<T>({
|
final class ChatScroll({
|
||||||
required final IList<T> historyItems,
|
required final IList<Event> historyItems,
|
||||||
required final IList<T> liveItems,
|
required final IList<Event> liveItems,
|
||||||
required final GlobalKey centerKey,
|
required final GlobalKey centerKey,
|
||||||
required final ListController historyListController,
|
required final ListController historyListController,
|
||||||
required final ListController liveListController,
|
required final ListController liveListController,
|
||||||
required final ScrollController scrollController,
|
required final ScrollController scrollController,
|
||||||
required final Future<void> Function(String id) jumpToId,
|
required final Future<void> Function(String id) jumpToId,
|
||||||
}) {
|
}) {
|
||||||
static ChatScroll<T> use<T>({
|
static ChatScroll use({
|
||||||
required AsyncValue<IList<T>?> controllerData,
|
required AsyncValue<RoomChat?> controllerData,
|
||||||
required String Function(T item) id,
|
|
||||||
required Future<void> Function() loadOlder,
|
required Future<void> Function() loadOlder,
|
||||||
required Future<void> Function() onReachedBottom,
|
required Future<void> Function() markRead,
|
||||||
}) {
|
}) {
|
||||||
final historyListController = useRef(ListController());
|
final historyListController = useRef(ListController());
|
||||||
final liveListController = useRef(ListController());
|
final liveListController = useRef(ListController());
|
||||||
|
|
@ -31,23 +32,23 @@ final class ChatScroll<T>({
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (anchorId.value == null) {
|
if (anchorId.value == null) {
|
||||||
if (controllerData case AsyncData(:final value?)
|
if (controllerData case AsyncData(:final value?)
|
||||||
when value.isNotEmpty) {
|
when value.events.isNotEmpty) {
|
||||||
anchorId.value = id(value.last);
|
anchorId.value = value.events.last.eventId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}, [controllerData]);
|
}, [controllerData]);
|
||||||
|
|
||||||
final ({IList<T> history, IList<T> live}) split = useMemoized(() {
|
final ({IList<Event> history, IList<Event> live}) split = useMemoized(() {
|
||||||
final items = controllerData.value;
|
final items = controllerData.value?.events;
|
||||||
final anchor = anchorIdValue;
|
final anchor = anchorIdValue;
|
||||||
|
|
||||||
if (items == null || anchor == null) {
|
if (items == null || anchor == null) {
|
||||||
return (history: const .empty(), live: const .empty());
|
return (history: const .empty(), live: const .empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
final anchorIndex = items.indexWhere((item) => id(item) == anchor);
|
final anchorIndex = items.indexWhere((item) => item.eventId == anchor);
|
||||||
|
|
||||||
if (anchorIndex == -1) {
|
if (anchorIndex == -1) {
|
||||||
return (history: const .empty(), live: items);
|
return (history: const .empty(), live: items);
|
||||||
|
|
@ -71,7 +72,7 @@ final class ChatScroll<T>({
|
||||||
if (position.extentAfter <= topThreshold) {
|
if (position.extentAfter <= topThreshold) {
|
||||||
await loadOlder();
|
await loadOlder();
|
||||||
} else if (position.extentBefore <= bottomThreshold) {
|
} else if (position.extentBefore <= bottomThreshold) {
|
||||||
await onReachedBottom();
|
await markRead();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,13 +83,13 @@ final class ChatScroll<T>({
|
||||||
return () {
|
return () {
|
||||||
scrollController.removeListener(checkPosition);
|
scrollController.removeListener(checkPosition);
|
||||||
};
|
};
|
||||||
}, [scrollController, controllerData, loadOlder, onReachedBottom]);
|
}, [scrollController, controllerData, loadOlder, markRead]);
|
||||||
|
|
||||||
Future<void> jumpToId(String itemId) async {
|
Future<void> jumpToId(String itemId) async {
|
||||||
if (!scrollController.hasClients) return;
|
if (!scrollController.hasClients) return;
|
||||||
|
|
||||||
final historyIndex = split.history.indexWhere(
|
final historyIndex = split.history.indexWhere(
|
||||||
(item) => id(item) == itemId,
|
(item) => item.eventId == itemId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (historyIndex != -1) {
|
if (historyIndex != -1) {
|
||||||
|
|
@ -105,7 +106,9 @@ final class ChatScroll<T>({
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
final liveIndex = split.live.indexWhere((item) => id(item) == itemId);
|
final liveIndex = split.live.indexWhere(
|
||||||
|
(item) => item.eventId == itemId,
|
||||||
|
);
|
||||||
|
|
||||||
if (liveIndex != -1) {
|
if (liveIndex != -1) {
|
||||||
// ignore: invalid_use_of_visible_for_testing_member
|
// ignore: invalid_use_of_visible_for_testing_member
|
||||||
|
|
|
||||||
31
lib/models/room_chat.dart
Normal file
31
lib/models/room_chat.dart
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
|
import "package:freezed_annotation/freezed_annotation.dart";
|
||||||
|
import "package:nexus/models/event.dart";
|
||||||
|
|
||||||
|
part "room_chat.freezed.dart";
|
||||||
|
part "room_chat.g.dart";
|
||||||
|
|
||||||
|
@Freezed(toJson: false, fromJson: false)
|
||||||
|
@JsonSerializable()
|
||||||
|
class const RoomChat({
|
||||||
|
required final IList<Event> events,
|
||||||
|
required final bool hasMore,
|
||||||
|
final HistoricalData? historicalData,
|
||||||
|
}) with _$RoomChat {
|
||||||
|
Map<String, Object?> toJson() => _$RoomChatToJson(this);
|
||||||
|
|
||||||
|
factory RoomChat.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$RoomChatFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Freezed(toJson: false, fromJson: false)
|
||||||
|
@JsonSerializable()
|
||||||
|
class const HistoricalData({
|
||||||
|
required final String end,
|
||||||
|
required final String start,
|
||||||
|
}) with _$HistoricalData {
|
||||||
|
Map<String, Object?> toJson() => _$HistoricalDataToJson(this);
|
||||||
|
|
||||||
|
factory HistoricalData.fromJson(Map<String, Object?> json) =>
|
||||||
|
_$HistoricalDataFromJson(json);
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ import "package:nexus/widgets/highlight_wrapper.dart";
|
||||||
import "package:super_sliver_list/super_sliver_list.dart";
|
import "package:super_sliver_list/super_sliver_list.dart";
|
||||||
|
|
||||||
class const ChatTimeline({
|
class const ChatTimeline({
|
||||||
required final ChatScroll<Event> scroll,
|
required final ChatScroll scroll,
|
||||||
required final Future<void> Function(String) jumpToId,
|
required final Future<void> Function(String) jumpToId,
|
||||||
required final IList<PopupMenuEntry> Function(Event) getEventOptions,
|
required final IList<PopupMenuEntry> Function(Event) getEventOptions,
|
||||||
required final String? highlightedEvent,
|
required final String? highlightedEvent,
|
||||||
|
|
|
||||||
|
|
@ -68,9 +68,8 @@ final class const RoomChat({
|
||||||
|
|
||||||
final scroll = ChatScroll.use(
|
final scroll = ChatScroll.use(
|
||||||
controllerData: controllerData,
|
controllerData: controllerData,
|
||||||
id: (event) => event.eventId,
|
|
||||||
loadOlder: notifier.loadOlder,
|
loadOlder: notifier.loadOlder,
|
||||||
onReachedBottom: () async {
|
markRead: () async {
|
||||||
final room = ref.read(
|
final room = ref.read(
|
||||||
RoomsController.provider.select((rooms) => rooms[roomId]),
|
RoomsController.provider.select((rooms) => rooms[roomId]),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue