feat: Animate in new events in timeline

This commit is contained in:
krille-chan 2023-12-26 20:08:07 +01:00
parent 8f3f5cdb8b
commit f07aba448d
No known key found for this signature in database
3 changed files with 120 additions and 78 deletions

View file

@ -304,6 +304,13 @@ class ChatController extends State<ChatPageWithRoom> {
Future<void>? loadTimelineFuture;
int? animateInEventIndex;
void onInsert(int i) {
// setState will be called by updateView() anyway
animateInEventIndex = i;
}
Future<void> _getTimeline({
String? eventContextId,
}) async {
@ -317,11 +324,15 @@ class ChatController extends State<ChatPageWithRoom> {
timeline = await room.getTimeline(
onUpdate: updateView,
eventContextId: eventContextId,
onInsert: onInsert,
);
} catch (e, s) {
Logs().w('Unable to load timeline on event ID $eventContextId', e, s);
if (!mounted) return;
timeline = await room.getTimeline(onUpdate: updateView);
timeline = await room.getTimeline(
onUpdate: updateView,
onInsert: onInsert,
);
if (!mounted) return;
if (e is TimeoutException || e is IOException) {
_showScrollUpMaterialBanner(eventContextId!);

View file

@ -27,6 +27,7 @@ class ChatEventList extends StatelessWidget {
final events = controller.timeline!.events
.where((event) => event.isVisibleInGui)
.toList();
final animateInEventIndex = controller.animateInEventIndex;
// create a map of eventId --> index to greatly improve performance of
// ListView's findChildIndexCallback
@ -101,6 +102,8 @@ class ChatEventList extends StatelessWidget {
// The message at this index:
final event = events[i];
final animateIn = animateInEventIndex != null &&
event == controller.timeline!.events[animateInEventIndex];
return AutoScrollTag(
key: ValueKey(event.eventId),
@ -108,6 +111,10 @@ class ChatEventList extends StatelessWidget {
controller: controller.scrollController,
child: Message(
event,
animateIn: animateIn,
resetAnimateIn: () {
controller.animateInEventIndex = null;
},
onSwipe: () => controller.replyAction(replyTo: event),
onInfoTab: controller.showEventInfo,
onAvatarTab: (Event event) => showAdaptiveBottomSheet(

View file

@ -32,6 +32,8 @@ class Message extends StatelessWidget {
final bool selected;
final Timeline timeline;
final bool highlightMarker;
final bool animateIn;
final void Function()? resetAnimateIn;
const Message(
this.event, {
@ -46,6 +48,8 @@ class Message extends StatelessWidget {
this.selected = false,
required this.timeline,
this.highlightMarker = false,
this.animateIn = false,
this.resetAnimateIn,
super.key,
});
@ -382,8 +386,22 @@ class Message extends StatelessWidget {
}
TapDownDetails? lastTapDownDetails;
final resetAnimateIn = this.resetAnimateIn;
var animateIn = this.animateIn;
return Center(
return StatefulBuilder(
builder: (context, setState) {
if (animateIn && resetAnimateIn != null) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
animateIn = false;
setState(resetAnimateIn);
});
}
return AnimatedSlide(
offset: Offset(0, animateIn ? 1 : 0),
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
child: Center(
child: Swipeable(
key: ValueKey(event.eventId),
background: const Padding(
@ -400,7 +418,9 @@ class Message extends StatelessWidget {
lastTapDownDetails = details;
},
onTap: () {
if (lastTapDownDetails?.kind == PointerDeviceKind.mouse) return;
if (lastTapDownDetails?.kind == PointerDeviceKind.mouse) {
return;
}
onSelect(event);
},
child: Stack(
@ -431,7 +451,8 @@ class Message extends StatelessWidget {
4,
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
shadowColor: Theme.of(context).appBarTheme.shadowColor,
shadowColor:
Theme.of(context).appBarTheme.shadowColor,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
@ -466,6 +487,9 @@ class Message extends StatelessWidget {
),
),
),
),
);
},
);
}
}