From f42c82295efcaa185733084f0c7890d671e92893 Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Mon, 24 Jun 2024 10:56:40 -0400 Subject: [PATCH 1/4] icons [nfc]: Add edited/moved marker icons. The icons are taken from the Vlad: https://chat.zulip.org/#narrow/stream/48-mobile/topic/.23F762.20edit.2Fmoved.20marker.20pill.20design/near/1891747 Signed-off-by: Zixuan James Li --- assets/icons/ZulipIcons.ttf | Bin 6452 -> 6676 bytes assets/icons/edited.svg | 3 +++ assets/icons/message_moved.svg | 3 +++ lib/widgets/icons.dart | 28 +++++++++++++++++----------- 4 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 assets/icons/edited.svg create mode 100644 assets/icons/message_moved.svg diff --git a/assets/icons/ZulipIcons.ttf b/assets/icons/ZulipIcons.ttf index 1fd2db9d1d79bebca46df70b34b7c91b16750966..e4ea5b0fc4f632087a274b469adeb44c8489b221 100644 GIT binary patch delta 1419 zcmb7EO-vJM7=FH)X$OHKKkam;g|@V%rD&oO16)=K8jXnw8q~vHuuCf?QfUM$9;9ma zum=yYqsDkJMmgZcgLv>jlml+|q{Jv^Z|i}B+&t{A`+U$V`oS9{c+FNo0j*ji?Kesuy8_eeTx zuU&XpYWodnC;%;cOT~Pl`g!9yxu1xE5)}=G(aZH3kuH_z=1*$h{-W(N_bbyg6F=lD zx62E3&`ia-a(=!dMnoqQu(?lM%9o3wm&5&lwaA19D>Jim58GFd0QT?XoLE{*1^T=S zScUoY`n#p=1ESB3iOJeBnh`?*KVt>!*u*xzii{W%KZ<#l+JFQGpt0}}Zwq1Alu>k| zi&g@S|I$0&d4yIb&uqj!UW6T?bF& zWQ9}$fn#1{;#AhnZBi|!BlB$`xo#%tR@PH*dz>AS4NUV?&Ws`6xk3G~=y1>IBkqD8 zbRX+C4{R`LdA(xAaqltTe+ zOMApIfM^m=oCf^_m;CBLU+3_*QJozO4~BI!Y-Y{upxlTzZ$_)zqa%xpqw2S$4S1@* z4}I4yV%2JT?Q^^s%$I@atWAhvjd3-6TYcX;X7Ur1u}>MIPwa84_K@i>7N+Kk1^1bq XZ3&c%v$Ofh;&^$cO4UbuoKEl$(HOkw delta 1188 zcmb7DPe>GD6o22&>>qWv+}+vPnf*IEyY6ljr4r_)5($egiBhM)X4Q2~R~I$4L#%WX zg2*PKZXUv8gewuTgn`{cI(3fl5aLCMZc)E){DSD#eEjA&?|bk4-h1EhJvqD8T_;9F zZL~rXbzUC6-hbNDxlhD;fzQ{5&vf7TG;~NLwIk}*Xw|AcsBR>PWZ2S+iRruLo#78t zL_Qx;V5c%}jV*rL*+lFYpr?WjpDgF`-2`%#>ca9R*RP+*t-)TOo*g}J-QFw1@NQva zsA?_OSRX?HrolF5tm=5=O2bFG;T6rBk$#lXvhr)vjY ztZ1j$M12lJC8$LAX@lO;F749~mSY2K41@Z}pdNA|B8;U_gjBFjF^VIHQR`p(QOB<# z%L|`MNheN2luQ9EgF+N_@(oJE(y0S+$vWgcq@k40DUrjrioiT%++;#Dgn z{2%e-h*OZILzc%Ka|Q}QO{D^MO)LY7+$W+uegjQpe1ILip20;a7k0KOmcSgy>pMWx8mn+L--+VKiYHQdxR#bMoWi5 z`;mO&q>55pKZ-^QPDvf@ny95Yrxr&21Zrm}V(-aWNPFxxx1}R5m5v&PSRC + + diff --git a/assets/icons/message_moved.svg b/assets/icons/message_moved.svg new file mode 100644 index 0000000000..591a988c34 --- /dev/null +++ b/assets/icons/message_moved.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/widgets/icons.dart b/lib/widgets/icons.dart index 23080a2257..9b3666a27c 100644 --- a/lib/widgets/icons.dart +++ b/lib/widgets/icons.dart @@ -42,38 +42,44 @@ abstract final class ZulipIcons { /// The Zulip custom icon "clock". static const IconData clock = IconData(0xf106, fontFamily: "Zulip Icons"); + /// The Zulip custom icon "edited". + static const IconData edited = IconData(0xf107, fontFamily: "Zulip Icons"); + /// The Zulip custom icon "globe". - static const IconData globe = IconData(0xf107, fontFamily: "Zulip Icons"); + static const IconData globe = IconData(0xf108, fontFamily: "Zulip Icons"); /// The Zulip custom icon "group_dm". - static const IconData group_dm = IconData(0xf108, fontFamily: "Zulip Icons"); + static const IconData group_dm = IconData(0xf109, fontFamily: "Zulip Icons"); /// The Zulip custom icon "hash_sign". - static const IconData hash_sign = IconData(0xf109, fontFamily: "Zulip Icons"); + static const IconData hash_sign = IconData(0xf10a, fontFamily: "Zulip Icons"); /// The Zulip custom icon "language". - static const IconData language = IconData(0xf10a, fontFamily: "Zulip Icons"); + static const IconData language = IconData(0xf10b, fontFamily: "Zulip Icons"); /// The Zulip custom icon "lock". - static const IconData lock = IconData(0xf10b, fontFamily: "Zulip Icons"); + static const IconData lock = IconData(0xf10c, fontFamily: "Zulip Icons"); + + /// The Zulip custom icon "message_moved". + static const IconData message_moved = IconData(0xf10d, fontFamily: "Zulip Icons"); /// The Zulip custom icon "mute". - static const IconData mute = IconData(0xf10c, fontFamily: "Zulip Icons"); + static const IconData mute = IconData(0xf10e, fontFamily: "Zulip Icons"); /// The Zulip custom icon "read_receipts". - static const IconData read_receipts = IconData(0xf10d, fontFamily: "Zulip Icons"); + static const IconData read_receipts = IconData(0xf10f, fontFamily: "Zulip Icons"); /// The Zulip custom icon "star_filled". - static const IconData star_filled = IconData(0xf10e, fontFamily: "Zulip Icons"); + static const IconData star_filled = IconData(0xf110, fontFamily: "Zulip Icons"); /// The Zulip custom icon "topic". - static const IconData topic = IconData(0xf10f, fontFamily: "Zulip Icons"); + static const IconData topic = IconData(0xf111, fontFamily: "Zulip Icons"); /// The Zulip custom icon "unmute". - static const IconData unmute = IconData(0xf110, fontFamily: "Zulip Icons"); + static const IconData unmute = IconData(0xf112, fontFamily: "Zulip Icons"); /// The Zulip custom icon "user". - static const IconData user = IconData(0xf111, fontFamily: "Zulip Icons"); + static const IconData user = IconData(0xf113, fontFamily: "Zulip Icons"); // END GENERATED ICON DATA } From e72f24012386759edd529ee94b1b2b6d8b33a132 Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Mon, 24 Jun 2024 11:02:36 -0400 Subject: [PATCH 2/4] theme [nfc]: Extract starColor to DesignVariables. Signed-off-by: Zixuan James Li --- lib/widgets/message_list.dart | 6 ++---- lib/widgets/theme.dart | 10 ++++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index b804fcab92..8c87d48082 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -896,12 +896,10 @@ class MessageWithPossibleSender extends StatelessWidget { final MessageListMessageItem item; - // TODO(#95) unchanged in dark theme? - static final _starColor = const HSLColor.fromAHSL(0.5, 47, 1, 0.41).toColor(); - @override Widget build(BuildContext context) { final store = PerAccountStoreWidget.of(context); + final designVariables = DesignVariables.of(context); final message = item.message; final sender = store.users[message.senderId]; @@ -980,7 +978,7 @@ class MessageWithPossibleSender extends StatelessWidget { // Design from Figma at: // https://www.figma.com/file/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=813%3A28817&mode=dev . ? Padding(padding: const EdgeInsets.only(top: 4), - child: Icon(ZulipIcons.star_filled, size: 16, color: _starColor)) + child: Icon(ZulipIcons.star_filled, size: 16, color: designVariables.star)) : null), ]), ]))); diff --git a/lib/widgets/theme.dart b/lib/widgets/theme.dart index c8742cae72..f35cc3e05f 100644 --- a/lib/widgets/theme.dart +++ b/lib/widgets/theme.dart @@ -123,6 +123,7 @@ class DesignVariables extends ThemeExtension { mainBackground: const Color(0xfff0f0f0), title: const Color(0xff1a1a1a), streamColorSwatches: StreamColorSwatches.light, + star: const HSLColor.fromAHSL(0.5, 47, 1, 0.41).toColor(), ); DesignVariables.dark() : @@ -133,6 +134,8 @@ class DesignVariables extends ThemeExtension { mainBackground: const Color(0xff1d1d1d), title: const Color(0xffffffff), streamColorSwatches: StreamColorSwatches.dark, + // TODO(#95) unchanged in dark theme? + star: const HSLColor.fromAHSL(0.5, 47, 1, 0.41).toColor(), ); DesignVariables._({ @@ -142,6 +145,7 @@ class DesignVariables extends ThemeExtension { required this.mainBackground, required this.title, required this.streamColorSwatches, + required this.star, }); /// The [DesignVariables] from the context's active theme. @@ -163,6 +167,9 @@ class DesignVariables extends ThemeExtension { // Not exactly from the Figma design, but from Vlad anyway. final StreamColorSwatches streamColorSwatches; + // Not named variables in Figma; taken from older Figma drafts, or elsewhere. + final Color star; + @override DesignVariables copyWith({ Color? bgTopBar, @@ -171,6 +178,7 @@ class DesignVariables extends ThemeExtension { Color? mainBackground, Color? title, StreamColorSwatches? streamColorSwatches, + Color? star, }) { return DesignVariables._( bgTopBar: bgTopBar ?? this.bgTopBar, @@ -179,6 +187,7 @@ class DesignVariables extends ThemeExtension { mainBackground: mainBackground ?? this.mainBackground, title: title ?? this.title, streamColorSwatches: streamColorSwatches ?? this.streamColorSwatches, + star: star ?? this.star, ); } @@ -194,6 +203,7 @@ class DesignVariables extends ThemeExtension { mainBackground: Color.lerp(mainBackground, other.mainBackground, t)!, title: Color.lerp(title, other.title, t)!, streamColorSwatches: StreamColorSwatches.lerp(streamColorSwatches, other.streamColorSwatches, t), + star: Color.lerp(star, other.star, t)!, ); } } From 6e8bfba73a72cd10aec052c44f10633d25c674a4 Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Fri, 5 Jul 2024 19:25:26 -0400 Subject: [PATCH 3/4] msglist: Fix star icon alignment issue. By aligning the row using `textBaseline`, we no longer need to apply the padding to the star. Fixes #616. Signed-off-by: Zixuan James Li --- lib/widgets/message_list.dart | 36 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index 8c87d48082..0a0251b60b 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -962,25 +962,23 @@ class MessageWithPossibleSender extends StatelessWidget { if (senderRow != null) Padding(padding: const EdgeInsets.fromLTRB(16, 2, 16, 0), child: senderRow), - Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - MessageContent(message: message, content: item.content), - if ((message.reactions?.total ?? 0) > 0) - ReactionChipsList(messageId: message.id, reactions: message.reactions!) - ])), - SizedBox(width: 16, - child: message.flags.contains(MessageFlag.starred) - // TODO(#157): fix how star marker aligns with message content - // Design from Figma at: - // https://www.figma.com/file/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=813%3A28817&mode=dev . - ? Padding(padding: const EdgeInsets.only(top: 4), - child: Icon(ZulipIcons.star_filled, size: 16, color: designVariables.star)) - : null), - ]), + Row(crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: localizedTextBaseline(context), + children: [ + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + MessageContent(message: message, content: item.content), + if ((message.reactions?.total ?? 0) > 0) + ReactionChipsList(messageId: message.id, reactions: message.reactions!) + ])), + SizedBox(width: 16, + child: message.flags.contains(MessageFlag.starred) + ? Icon(ZulipIcons.star_filled, size: 16, color: designVariables.star) + : null), + ]), ]))); } } From 42d53c19e5ed2dda0744a79f958ee0d2284ed4de Mon Sep 17 00:00:00 2001 From: Zixuan James Li Date: Mon, 24 Jun 2024 16:52:50 -0400 Subject: [PATCH 4/4] ui: Add edited/moved marker. This adds basic support to displaying a edited/moved marker next to the message content. As of now this is implemented without the swipe gesture control that would expand the marker. Partially addresses #171. Signed-off-by: Zixuan James Li --- lib/widgets/edit_state_marker.dart | 74 +++++++++++++++++++++++ lib/widgets/message_list.dart | 21 +++---- lib/widgets/theme.dart | 8 +++ test/widgets/message_list_test.dart | 94 +++++++++++++++++++++++++++++ 4 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 lib/widgets/edit_state_marker.dart diff --git a/lib/widgets/edit_state_marker.dart b/lib/widgets/edit_state_marker.dart new file mode 100644 index 0000000000..0c6abfe442 --- /dev/null +++ b/lib/widgets/edit_state_marker.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +import '../api/model/model.dart'; +import 'icons.dart'; +import 'text.dart'; +import 'theme.dart'; + +class EditStateMarker extends StatelessWidget { + const EditStateMarker({ + super.key, + required this.editState, + required this.children, + }); + + final MessageEditState editState; + final List children; + + @override + Widget build(BuildContext context) { + final hasMarker = editState != MessageEditState.none; + + return Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: localizedTextBaseline(context), + children: [ + hasMarker + ? _EditStateMarkerPill(editState: editState) + : const SizedBox(width: _EditStateMarkerPill.widthCollapsed), + ...children, + ], + ); + } +} + +class _EditStateMarkerPill extends StatelessWidget { + const _EditStateMarkerPill({required this.editState}); + + final MessageEditState editState; + + /// The minimum width of the marker. + // Currently, only the collapsed state of the marker has been implemented, + // where only the marker icon, not the marker text, is visible. + static const double widthCollapsed = 16; + + @override + Widget build(BuildContext context) { + final designVariables = DesignVariables.of(context); + + final IconData icon; + final Offset offset; + switch (editState) { + case MessageEditState.none: + assert(false); + return const SizedBox(width: widthCollapsed); + case MessageEditState.edited: + icon = ZulipIcons.edited; + // These offsets are chosen ad hoc, but give a good vertical alignment + // of the icons with the first line of the message, when the message + // begins with a paragraph, at default text scaling. See: + // https://github.com/zulip/zulip-flutter/pull/762#issuecomment-2232041922 + offset = const Offset(0, 2); + case MessageEditState.moved: + icon = ZulipIcons.message_moved; + offset = const Offset(0, 3); + } + + return ConstrainedBox( + constraints: const BoxConstraints(maxWidth: widthCollapsed), + child: Transform.translate( + offset: offset, + child: Icon( + icon, size: 16, color: designVariables.editedMovedMarkerCollapsed))); + } +} diff --git a/lib/widgets/message_list.dart b/lib/widgets/message_list.dart index 0a0251b60b..329dabf58d 100644 --- a/lib/widgets/message_list.dart +++ b/lib/widgets/message_list.dart @@ -21,6 +21,7 @@ import 'page.dart'; import 'profile.dart'; import 'sticky_header.dart'; import 'store.dart'; +import 'edit_state_marker.dart'; import 'text.dart'; import 'theme.dart'; @@ -962,18 +963,16 @@ class MessageWithPossibleSender extends StatelessWidget { if (senderRow != null) Padding(padding: const EdgeInsets.fromLTRB(16, 2, 16, 0), child: senderRow), - Row(crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: localizedTextBaseline(context), + EditStateMarker( + editState: message.editState, children: [ - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - MessageContent(message: message, content: item.content), - if ((message.reactions?.total ?? 0) > 0) - ReactionChipsList(messageId: message.id, reactions: message.reactions!) - ])), + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + MessageContent(message: message, content: item.content), + if ((message.reactions?.total ?? 0) > 0) + ReactionChipsList(messageId: message.id, reactions: message.reactions!) + ])), SizedBox(width: 16, child: message.flags.contains(MessageFlag.starred) ? Icon(ZulipIcons.star_filled, size: 16, color: designVariables.star) diff --git a/lib/widgets/theme.dart b/lib/widgets/theme.dart index f35cc3e05f..c9415e7be2 100644 --- a/lib/widgets/theme.dart +++ b/lib/widgets/theme.dart @@ -124,6 +124,7 @@ class DesignVariables extends ThemeExtension { title: const Color(0xff1a1a1a), streamColorSwatches: StreamColorSwatches.light, star: const HSLColor.fromAHSL(0.5, 47, 1, 0.41).toColor(), + editedMovedMarkerCollapsed: const Color.fromARGB(128, 146, 167, 182), ); DesignVariables.dark() : @@ -136,6 +137,8 @@ class DesignVariables extends ThemeExtension { streamColorSwatches: StreamColorSwatches.dark, // TODO(#95) unchanged in dark theme? star: const HSLColor.fromAHSL(0.5, 47, 1, 0.41).toColor(), + // TODO(#95) need dark-theme color + editedMovedMarkerCollapsed: const Color.fromARGB(128, 146, 167, 182), ); DesignVariables._({ @@ -146,6 +149,7 @@ class DesignVariables extends ThemeExtension { required this.title, required this.streamColorSwatches, required this.star, + required this.editedMovedMarkerCollapsed, }); /// The [DesignVariables] from the context's active theme. @@ -169,6 +173,7 @@ class DesignVariables extends ThemeExtension { // Not named variables in Figma; taken from older Figma drafts, or elsewhere. final Color star; + final Color editedMovedMarkerCollapsed; @override DesignVariables copyWith({ @@ -179,6 +184,7 @@ class DesignVariables extends ThemeExtension { Color? title, StreamColorSwatches? streamColorSwatches, Color? star, + Color? editedMovedMarkerCollapsed, }) { return DesignVariables._( bgTopBar: bgTopBar ?? this.bgTopBar, @@ -188,6 +194,7 @@ class DesignVariables extends ThemeExtension { title: title ?? this.title, streamColorSwatches: streamColorSwatches ?? this.streamColorSwatches, star: star ?? this.star, + editedMovedMarkerCollapsed: editedMovedMarkerCollapsed ?? this.editedMovedMarkerCollapsed, ); } @@ -204,6 +211,7 @@ class DesignVariables extends ThemeExtension { title: Color.lerp(title, other.title, t)!, streamColorSwatches: StreamColorSwatches.lerp(streamColorSwatches, other.streamColorSwatches, t), star: Color.lerp(star, other.star, t)!, + editedMovedMarkerCollapsed: Color.lerp(editedMovedMarkerCollapsed, other.editedMovedMarkerCollapsed, t)!, ); } } diff --git a/test/widgets/message_list_test.dart b/test/widgets/message_list_test.dart index b583446d78..41e19b8f3c 100644 --- a/test/widgets/message_list_test.dart +++ b/test/widgets/message_list_test.dart @@ -16,6 +16,7 @@ import 'package:zulip/model/localizations.dart'; import 'package:zulip/model/narrow.dart'; import 'package:zulip/model/store.dart'; import 'package:zulip/widgets/content.dart'; +import 'package:zulip/widgets/emoji_reaction.dart'; import 'package:zulip/widgets/icons.dart'; import 'package:zulip/widgets/message_list.dart'; import 'package:zulip/widgets/store.dart'; @@ -576,6 +577,99 @@ void main() { }); }); + group('EditStateMarker', () { + void checkMarkersCount({required int edited, required int moved}) { + check(find.byIcon(ZulipIcons.edited).evaluate()).length.equals(edited); + check(find.byIcon(ZulipIcons.message_moved).evaluate()).length.equals(moved); + } + + testWidgets('no edited or moved messages', (WidgetTester tester) async { + final message = eg.streamMessage(); + await setupMessageListPage(tester, messages: [message]); + checkMarkersCount(edited: 0, moved: 0); + }); + + testWidgets('edited and moved messages from events', (WidgetTester tester) async { + final message = eg.streamMessage(); + final message2 = eg.streamMessage(); + await setupMessageListPage(tester, messages: [message, message2]); + checkMarkersCount(edited: 0, moved: 0); + + await store.handleEvent(eg.updateMessageEditEvent(message, renderedContent: 'edited')); + await tester.pump(); + checkMarkersCount(edited: 1, moved: 0); + + await store.handleEvent(eg.updateMessageMoveEvent( + [message, message2], origTopic: 'old', newTopic: 'new')); + await tester.pump(); + checkMarkersCount(edited: 1, moved: 1); + + await store.handleEvent(eg.updateMessageEditEvent(message2, renderedContent: 'edited')); + await tester.pump(); + checkMarkersCount(edited: 2, moved: 0); + }); + + List> captureMessageRects( + WidgetTester tester, + List messages, + Message targetMessage, + ) { + assert(messages.contains(targetMessage)); + final List> result = []; + for (final message in messages) { + final baseFinder = find.byWidgetPredicate( + (widget) => widget is MessageWithPossibleSender + && widget.item.message.id == message.id); + + Rect getRectMatching(Finder matching) { + final finder = find.descendant( + of: baseFinder, matching: matching)..tryEvaluate(); + check(finder.found, because: '${message.content}: $matching') + .length.equals(1); + return tester.getRect(finder.first); + } + + result.add([ + ('MessageWithPossibleSender', tester.getRect(baseFinder)), + ('MessageContent', getRectMatching(find.byType(MessageContent))), + ('Paragraph', getRectMatching(find.byType(Paragraph))), + if (message.id == targetMessage.id) ...[ + ('Star', getRectMatching(find.byIcon(ZulipIcons.star_filled))), + ('ReactionChipsList', getRectMatching(find.byType(ReactionChipsList))), + ], + ]); + } + return result; + } + + testWidgets('edit state updates do not affect layout', (WidgetTester tester) async { + final messages = [ + eg.streamMessage(), + eg.streamMessage( + reactions: [eg.unicodeEmojiReaction, eg.realmEmojiReaction], + flags: [MessageFlag.starred]), + eg.streamMessage(), + ]; + final StreamMessage messageWithMarker = messages[1]; + await setupMessageListPage(tester, messages: messages); + final rectsBefore = captureMessageRects(tester, messages, messageWithMarker); + checkMarkersCount(edited: 0, moved: 0); + + await store.handleEvent(eg.updateMessageMoveEvent( + [messageWithMarker], origTopic: 'old', newTopic: messageWithMarker.topic)); + await tester.pump(); + check(captureMessageRects(tester, messages, messageWithMarker)) + .deepEquals(rectsBefore); + checkMarkersCount(edited: 0, moved: 1); + + await store.handleEvent(eg.updateMessageEditEvent(messageWithMarker)); + await tester.pump(); + check(captureMessageRects(tester, messages, messageWithMarker)) + .deepEquals(rectsBefore); + checkMarkersCount(edited: 1, moved: 0); + }); + }); + group('_UnreadMarker animations', () { // TODO: Improve animation state testing so it is less tied to // implementation details and more focused on output, see: