Skip to content

Commit

Permalink
Jump to specific event in timeline (#301)
Browse files Browse the repository at this point in the history
  • Loading branch information
Airyzz authored Jul 2, 2024
1 parent fdb2996 commit 4b6920d
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,25 @@ class RoomTimelineWidgetViewState extends State<RoomTimelineWidgetView> {
int recentItemsCount = 0;
int historyItemsCount = 0;
bool firstFrame = true;
bool animatingToBottom = false;

late ScrollController controller;
late List<(GlobalKey, String)> eventKeys;
bool animatingToBottom = false;

GlobalKey firstFrameScrollViewKey = GlobalKey();
GlobalKey scrollViewKey = GlobalKey();
GlobalKey centerKey = GlobalKey();
GlobalKey recentItemsKey = GlobalKey();
GlobalKey overlayKey = GlobalKey();
GlobalKey stackKey = GlobalKey();

LayerLink selectedEventLayerLink = LayerLink();
SelectableEventViewWidget? selectedEventView;

String? highlightedEventId;
TimelineViewEntryState? highlightedEventState;
GlobalKey? highlightedEventOffstageKey;
int? highlightedEventOffstageIndex;
late List<StreamSubscription> subscriptions;

bool wasLastScrollAttachedToBottom = false;
Expand All @@ -81,6 +86,7 @@ class RoomTimelineWidgetViewState extends State<RoomTimelineWidgetView> {
widget.timeline.events
.map((e) => (GlobalKey(debugLabel: e.eventId), e.eventId)),
growable: true);

super.initState();
}

Expand All @@ -106,9 +112,11 @@ class RoomTimelineWidgetViewState extends State<RoomTimelineWidgetView> {
}

if (index == 0) {
if (attachedToBottom || animatingToBottom) {
if (attachedToBottom) {
WidgetsBinding.instance.addPostFrameCallback((_) {
animateAndSnapToBottom();
controller.animateTo(controller.position.minScrollExtent,
duration: const Duration(milliseconds: 500),
curve: Curves.easeOutExpo);
});

widget.markAsRead?.call(widget.timeline.events[0]);
Expand Down Expand Up @@ -156,6 +164,7 @@ class RoomTimelineWidgetViewState extends State<RoomTimelineWidgetView> {
if (controller.hasClients) {
double extent = controller.position.minScrollExtent;
controller = ScrollController(initialScrollOffset: extent);
scrollViewKey = GlobalKey();
controller.addListener(onScroll);
widget.onAttachedToBottom?.call();
setState(() {
Expand Down Expand Up @@ -252,6 +261,7 @@ class RoomTimelineWidgetViewState extends State<RoomTimelineWidgetView> {
// onExit: (_) => deselectEvent(),
child: ClipRect(
child: Stack(
key: stackKey,
children: [
Offstage(
offstage: firstFrame,
Expand Down Expand Up @@ -290,6 +300,8 @@ class RoomTimelineWidgetViewState extends State<RoomTimelineWidgetView> {
setEditingEvent: widget.setEditingEvent,
setReplyingEvent: widget.setReplyingEvent,
isThreadTimeline: widget.isThreadTimeline,
highlightedEventId: highlightedEventId,
jumpToEvent: jumpToEvent,
initialIndex: timelineIndex),
);
},
Expand Down Expand Up @@ -335,6 +347,8 @@ class RoomTimelineWidgetViewState extends State<RoomTimelineWidgetView> {
setEditingEvent: widget.setEditingEvent,
setReplyingEvent: widget.setReplyingEvent,
isThreadTimeline: widget.isThreadTimeline,
highlightedEventId: highlightedEventId,
jumpToEvent: jumpToEvent,
initialIndex: timelineIndex),
);
},
Expand All @@ -358,11 +372,96 @@ class RoomTimelineWidgetViewState extends State<RoomTimelineWidgetView> {
key: overlayKey,
showMessageMenu: Layout.desktop,
jumpToLatest: animateAndSnapToBottom,
link: selectedEventLayerLink)
link: selectedEventLayerLink),
if (highlightedEventOffstageIndex != null &&
highlightedEventOffstageKey != null)
Offstage(
offstage: true,
child: Column(
children: [
Container(
color: Colors.red,
child: TimelineViewEntry(
key: highlightedEventOffstageKey,
timeline: widget.timeline,
isThreadTimeline: widget.isThreadTimeline,
initialIndex: highlightedEventOffstageIndex!,
),
),
],
),
)
],
),
),
),
);
}

void jumpToEvent(String eventId) {
int index =
widget.timeline.events.indexWhere((event) => event.eventId == eventId);
if (index == -1) {
return;
}

if (highlightedEventState?.mounted == true) {
highlightedEventState!.setHighlighted(false);
highlightedEventState = null;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
var key = eventKeys[index].$1;
final state = key.currentState;

if (state is TimelineViewEntryState) {
state.setHighlighted(true);
highlightedEventState = state;
}

var boundsSize = stackKey.globalPaintBounds?.height;
var offset = 0.0;
if (boundsSize != null) {
offset = -(boundsSize / 2);
}

final eventHeight =
highlightedEventOffstageKey?.globalPaintBounds?.height;
if (eventHeight != null) {
Log.d("Measured height of widget: $eventHeight");
offset += eventHeight / 2;
}

controller.animateTo(offset,
duration: const Duration(milliseconds: 300),
curve: Easing.emphasizedDecelerate);

if (mounted) {
setState(() {
highlightedEventOffstageIndex = null;
highlightedEventOffstageKey = null;
});
}
});

setState(() {
recentItemsCount = index;
historyItemsCount = widget.timeline.events.length - recentItemsCount;
highlightedEventId = widget.timeline.events[index].eventId;
highlightedEventOffstageIndex = index;
highlightedEventOffstageKey = GlobalKey();
});
}
}

extension GlobalKeyExtension on GlobalKey {
Rect? get globalPaintBounds {
final renderObject = currentContext?.findRenderObject();
final translation = renderObject?.getTransformTo(null).getTranslation();
if (translation != null && renderObject?.paintBounds != null) {
final offset = Offset(translation.x, translation.y);
return renderObject!.paintBounds.shift(offset);
} else {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ class TimelineEventViewMessage extends StatefulWidget {
required this.timeline,
this.isThreadTimeline = false,
this.overrideShowSender = false,
this.jumpToEvent,
this.detailed = false,
required this.initialIndex});

final Function(String eventId)? jumpToEvent;

final Timeline timeline;
final int initialIndex;
final bool overrideShowSender;
Expand Down Expand Up @@ -55,6 +58,7 @@ class _TimelineEventViewMessageState extends State<TimelineEventViewMessage>
bool hasReactions = false;
bool isInResponse = false;
bool showSender = false;
late String eventId;
late String currentUserIdentifier;
late DateTime sentTime;

Expand Down Expand Up @@ -100,6 +104,7 @@ class _TimelineEventViewMessageState extends State<TimelineEventViewMessage>
? TimelineEventViewReply(
timeline: widget.timeline,
index: index,
jumpToEvent: widget.jumpToEvent,
)
: null,
reactions: hasReactions
Expand Down Expand Up @@ -140,6 +145,7 @@ class _TimelineEventViewMessageState extends State<TimelineEventViewMessage>
index = eventIndex;
var event = widget.timeline.events[eventIndex];
var sender = widget.timeline.room.getMemberOrFallback(event.senderId);
eventId = event.eventId;

senderName = sender.displayName;
senderAvatar = sender.avatar;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ class TimelineEventViewReply extends StatefulWidget {
{super.key,
required this.timeline,
required this.index,
this.jumpToEvent,
this.avatarSize = 32});
final Timeline timeline;
final Function(String eventId)? jumpToEvent;
final int index;
final double avatarSize;

Expand All @@ -24,6 +26,7 @@ class _TimelineEventViewReplyState extends State<TimelineEventViewReply> {
Color? senderColor;

bool loading = false;
String? replyEventId;

@override
void initState() {
Expand All @@ -49,67 +52,79 @@ class _TimelineEventViewReplyState extends State<TimelineEventViewReply> {

void setStateFromEvent(TimelineEvent event) {
setState(() {
replyEventId = event.eventId;
var sender = widget.timeline.room.getMemberOrFallback(event.senderId);
senderName = sender.displayName;
senderColor = sender.defaultColor;
body = event.body ?? "";
body = event.body ?? event.attachments?.firstOrNull?.name ?? "";
loading = false;
});
}

@override
Widget build(BuildContext context) {
BenchmarkValues.numTimelineReplyBodyBuilt += 1;
return IntrinsicHeight(
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox(
width: 45,
child: SizedBox.expand(
child: CustomPaint(
painter: ReplyLinePainter2(
pathColor: material.Theme.of(context).colorScheme.secondary,
avatarSize: widget.avatarSize),
),
return ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => widget.jumpToEvent?.call(replyEventId!),
child: IntrinsicHeight(
child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox(
width: 45,
child: SizedBox.expand(
child: CustomPaint(
painter: ReplyLinePainter2(
pathColor:
material.Theme.of(context).colorScheme.secondary,
avatarSize: widget.avatarSize),
),
),
),
Flexible(
child: RichText(
overflow: TextOverflow.ellipsis,
maxLines: 2,
text: TextSpan(children: [
TextSpan(
text: "${senderName ?? "Loading"} ",
style: TextStyle(
color: tiamat.Text.adjustColor(
context, senderColor ?? Colors.white))),
TextSpan(
text: body ?? "Unknown",
style: TextStyle(
color: material.Theme.of(context)
.colorScheme
.secondary)),
])),
)
// Column(
// children: [
// tiamat.Text(
// senderName ?? "Loading",
// color: senderColor,
// autoAdjustBrightness: true,
// ),
// ],
// ),
// Flexible(
// child: Padding(
// padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
// child: tiamat.Text(
// body ?? "Unknown",
// maxLines: 2,
// overflow: TextOverflow.ellipsis,
// color: material.Theme.of(context).colorScheme.secondary,
// ),
// ),
// ),
]),
),
),
Flexible(
child: RichText(
overflow: TextOverflow.ellipsis,
maxLines: 2,
text: TextSpan(children: [
TextSpan(
text: "${senderName ?? "Loading"} ",
style: TextStyle(
color: tiamat.Text.adjustColor(
context, senderColor ?? Colors.white))),
TextSpan(
text: body ?? "Unknown",
style: TextStyle(
color:
material.Theme.of(context).colorScheme.secondary)),
])),
)
// Column(
// children: [
// tiamat.Text(
// senderName ?? "Loading",
// color: senderColor,
// autoAdjustBrightness: true,
// ),
// ],
// ),
// Flexible(
// child: Padding(
// padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
// child: tiamat.Text(
// body ?? "Unknown",
// maxLines: 2,
// overflow: TextOverflow.ellipsis,
// color: material.Theme.of(context).colorScheme.secondary,
// ),
// ),
// ),
]),
),
);
}
}
Expand Down
Loading

0 comments on commit 4b6920d

Please sign in to comment.