fix timeline weirdness with zero extents
This commit is contained in:
parent
272241f338
commit
ecac985d90
3 changed files with 20 additions and 77 deletions
|
|
@ -3,7 +3,6 @@ import "dart:math";
|
||||||
|
|
||||||
import "package:collection/collection.dart";
|
import "package:collection/collection.dart";
|
||||||
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
import "package:fast_immutable_collections/fast_immutable_collections.dart";
|
||||||
import "package:flutter/foundation.dart";
|
|
||||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||||
import "package:fluttertagger/fluttertagger.dart";
|
import "package:fluttertagger/fluttertagger.dart";
|
||||||
import "package:nexus/controllers/attachment.dart";
|
import "package:nexus/controllers/attachment.dart";
|
||||||
|
|
@ -84,14 +83,6 @@ class RoomChatController(final ChatParams info)
|
||||||
.where((rowId) => room.events[rowId]?.threadRoot == null)
|
.where((rowId) => room.events[rowId]?.threadRoot == null)
|
||||||
.toIList();
|
.toIList();
|
||||||
|
|
||||||
// TODO: remove, debugging replies not showing
|
|
||||||
debugPrint(
|
|
||||||
"[timeline] build ${info.roomId}: "
|
|
||||||
"syncedTail=${room.timeline.entries.sortedBy<num>((entry) => entry.key).reversed.take(5).map((entry) => "${entry.key}:${entry.value}").toList()} "
|
|
||||||
"clientSticky=${room.clientSticky.toList()} "
|
|
||||||
"chatTail=${timeline.reversed.take(5).toList()}",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (info.contextualEvent == null ||
|
if (info.contextualEvent == null ||
|
||||||
timeline.contains(info.contextualEvent)) {
|
timeline.contains(info.contextualEvent)) {
|
||||||
ref.watch(
|
ref.watch(
|
||||||
|
|
@ -465,11 +456,6 @@ class RoomChatController(final ChatParams info)
|
||||||
|
|
||||||
/// Shows [event], which was just sent, until it comes down sync.
|
/// Shows [event], which was just sent, until it comes down sync.
|
||||||
void addSent(Event? event) {
|
void addSent(Event? event) {
|
||||||
// TODO: remove, debugging replies not showing
|
|
||||||
debugPrint(
|
|
||||||
"[timeline] addSent: rowId=${event?.rowId} eventId=${event?.eventId} "
|
|
||||||
"alreadySynced=${ref.read(RoomsController.provider)[info.roomId]?.timeline.values.contains(event?.rowId)}",
|
|
||||||
);
|
|
||||||
if (event case final event?) {
|
if (event case final event?) {
|
||||||
ref
|
ref
|
||||||
.read(RoomsController.provider.notifier)
|
.read(RoomsController.provider.notifier)
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,6 @@ import "package:nexus/models/room_chat.dart";
|
||||||
|
|
||||||
typedef JumpToEvent = Future<void> Function(int rowId);
|
typedef JumpToEvent = Future<void> Function(int rowId);
|
||||||
|
|
||||||
/// Identifies the row for the event with [rowId], so it can be found to
|
|
||||||
/// scroll to it, and keeps its element when rows are added before it.
|
|
||||||
final class EventRowKey extends GlobalKey {
|
|
||||||
EventRowKey(this.rowId) : super.constructor();
|
|
||||||
|
|
||||||
final int rowId;
|
|
||||||
}
|
|
||||||
|
|
||||||
final class ChatScroll({
|
final class ChatScroll({
|
||||||
required final IList<int> historyRows,
|
required final IList<int> historyRows,
|
||||||
required final IList<int> liveRows,
|
required final IList<int> liveRows,
|
||||||
|
|
@ -28,7 +20,7 @@ final class ChatScroll({
|
||||||
|
|
||||||
required final JumpToEvent jumpToEvent,
|
required final JumpToEvent jumpToEvent,
|
||||||
required final Future<void> Function() jumpToBottom,
|
required final Future<void> Function() jumpToBottom,
|
||||||
required final EventRowKey Function(int eventRowId) keyFor,
|
required final GlobalKey Function(int eventRowId) keyFor,
|
||||||
}) {
|
}) {
|
||||||
factory use({
|
factory use({
|
||||||
required AsyncValue<RoomChat?> controllerData,
|
required AsyncValue<RoomChat?> controllerData,
|
||||||
|
|
@ -50,9 +42,9 @@ final class ChatScroll({
|
||||||
final scrollController = useScrollController();
|
final scrollController = useScrollController();
|
||||||
final centerKey = useMemoized(GlobalKey.new);
|
final centerKey = useMemoized(GlobalKey.new);
|
||||||
|
|
||||||
final itemKeys = useMemoized(() => <int, EventRowKey>{}, []);
|
final itemKeys = useMemoized(() => <int, GlobalKey>{}, []);
|
||||||
EventRowKey keyFor(int eventRowId) =>
|
GlobalKey keyFor(int eventRowId) =>
|
||||||
itemKeys.putIfAbsent(eventRowId, () => EventRowKey(eventRowId));
|
itemKeys.putIfAbsent(eventRowId, GlobalKey.new);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (anchorId.value == null) {
|
if (anchorId.value == null) {
|
||||||
|
|
@ -109,13 +101,6 @@ final class ChatScroll({
|
||||||
final anchorIndex = items.indexOf(anchor);
|
final anchorIndex = items.indexOf(anchor);
|
||||||
|
|
||||||
final historyLength = anchorIndex == -1 ? items.length : anchorIndex + 1;
|
final historyLength = anchorIndex == -1 ? items.length : anchorIndex + 1;
|
||||||
// TODO: remove, debugging replies not showing
|
|
||||||
debugPrint(
|
|
||||||
"[timeline] split: anchor=$anchor anchorIndex=$anchorIndex "
|
|
||||||
"length=${items.length} "
|
|
||||||
"historyTail=${items.take(historyLength).toList().reversed.take(3).toList()} "
|
|
||||||
"live=${items.skip(historyLength).toList()}",
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
history: items.take(historyLength).toIList().reversed.toIList(),
|
history: items.take(historyLength).toIList().reversed.toIList(),
|
||||||
live: items.skip(historyLength).toIList(),
|
live: items.skip(historyLength).toIList(),
|
||||||
|
|
@ -128,27 +113,13 @@ final class ChatScroll({
|
||||||
void updateAtBottom() {
|
void updateAtBottom() {
|
||||||
if (!hookContext.mounted || !scrollController.hasClients) return;
|
if (!hookContext.mounted || !scrollController.hasClients) return;
|
||||||
|
|
||||||
final position = scrollController.position;
|
final isAtBottom = scrollController.position.extentBefore <= 0;
|
||||||
final isAtBottom = position.extentBefore <= 0;
|
if (isAtBottom != atBottom.value) atBottom.value = isAtBottom;
|
||||||
if (isAtBottom != atBottom.value) {
|
|
||||||
// TODO: remove, debugging replies not showing
|
|
||||||
debugPrint(
|
|
||||||
"[timeline] atBottom: $isAtBottom pixels=${position.pixels} "
|
|
||||||
"min=${position.minScrollExtent} anchor=${anchorId.value} "
|
|
||||||
"live=${split.live.length}",
|
|
||||||
);
|
|
||||||
atBottom.value = isAtBottom;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
final previous = previousLastRowId.value;
|
final previous = previousLastRowId.value;
|
||||||
previousLastRowId.value = lastRowId;
|
previousLastRowId.value = lastRowId;
|
||||||
// TODO: remove, debugging replies not showing
|
|
||||||
debugPrint(
|
|
||||||
"[timeline] lastRowId: previous=$previous last=$lastRowId "
|
|
||||||
"atBottom=${atBottom.value} contextualEvent=${contextualEvent.value}",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (previous == null || lastRowId == null || previous == lastRowId) {
|
if (previous == null || lastRowId == null || previous == lastRowId) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -156,11 +127,6 @@ final class ChatScroll({
|
||||||
if (!atBottom.value || contextualEvent.value != null) return null;
|
if (!atBottom.value || contextualEvent.value != null) return null;
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// TODO: remove, debugging replies not showing
|
|
||||||
debugPrint(
|
|
||||||
"[timeline] re-anchoring to $lastRowId "
|
|
||||||
"hasClients=${scrollController.hasClients}",
|
|
||||||
);
|
|
||||||
if (!scrollController.hasClients) return;
|
if (!scrollController.hasClients) return;
|
||||||
|
|
||||||
anchorId.value = lastRowId;
|
anchorId.value = lastRowId;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,10 @@ class const ChatTimeline({
|
||||||
int? previousEventRowId, {
|
int? previousEventRowId, {
|
||||||
required Key key,
|
required Key key,
|
||||||
}) => HighlightWrapper(
|
}) => HighlightWrapper(
|
||||||
EventRenderer(
|
// To work around an AWFUL SuperSliverList bug
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(minHeight: 0.01),
|
||||||
|
child: EventRenderer(
|
||||||
eventRowId,
|
eventRowId,
|
||||||
previousEventRowId: previousEventRowId,
|
previousEventRowId: previousEventRowId,
|
||||||
roomId: chat.roomId,
|
roomId: chat.roomId,
|
||||||
|
|
@ -36,20 +39,11 @@ class const ChatTimeline({
|
||||||
readReceiptsChat: chat,
|
readReceiptsChat: chat,
|
||||||
threadRoot: chat.threadRoot,
|
threadRoot: chat.threadRoot,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
key: key,
|
key: key,
|
||||||
isHighlighted: scroll.highlightedEvent == eventRowId,
|
isHighlighted: scroll.highlightedEvent == eventRowId,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Where the row with [key] now is in [rows], so its element moves with it
|
|
||||||
/// when rows are added before it, rather than being rebuilt by index.
|
|
||||||
int? indexOf(IList<int> rows, Key key) => switch (key) {
|
|
||||||
EventRowKey(:final rowId) => switch (rows.indexOf(rowId)) {
|
|
||||||
-1 => null,
|
|
||||||
final index => index,
|
|
||||||
},
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => LayoutBuilder(
|
Widget build(BuildContext context) => LayoutBuilder(
|
||||||
builder: (context, constraints) => TweenAnimationBuilder(
|
builder: (context, constraints) => TweenAnimationBuilder(
|
||||||
|
|
@ -76,7 +70,6 @@ class const ChatTimeline({
|
||||||
|
|
||||||
SuperSliverList.builder(
|
SuperSliverList.builder(
|
||||||
itemCount: scroll.liveRows.length,
|
itemCount: scroll.liveRows.length,
|
||||||
findChildIndexCallback: (key) => indexOf(scroll.liveRows, key),
|
|
||||||
itemBuilder: (_, index) => eventRow(
|
itemBuilder: (_, index) => eventRow(
|
||||||
scroll.liveRows[index],
|
scroll.liveRows[index],
|
||||||
index > 0
|
index > 0
|
||||||
|
|
@ -89,8 +82,6 @@ class const ChatTimeline({
|
||||||
SuperSliverList.builder(
|
SuperSliverList.builder(
|
||||||
key: scroll.centerKey,
|
key: scroll.centerKey,
|
||||||
itemCount: scroll.historyRows.length,
|
itemCount: scroll.historyRows.length,
|
||||||
findChildIndexCallback: (key) =>
|
|
||||||
indexOf(scroll.historyRows, key),
|
|
||||||
itemBuilder: (_, index) => eventRow(
|
itemBuilder: (_, index) => eventRow(
|
||||||
scroll.historyRows[index],
|
scroll.historyRows[index],
|
||||||
scroll.historyRows.getOrNull(index + 1),
|
scroll.historyRows.getOrNull(index + 1),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue