Skip to content

Commit f7ab0ba

Browse files
committed
msglist: Implement new edited/moved label design.
This replaces the gutter edit state marker design. Since it leads to message displacement, a previous test case is no longer applicable. We also inline part of what was previously in edit_state_marker.dart, because the new design simplifies the implementation. The design variables are taken from the Figma design: https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=3038-56393&t=WMT80mwuFaruNbVf-1 Signed-off-by: Zixuan James Li <[email protected]>
1 parent f7bc237 commit f7ab0ba

File tree

5 files changed

+41
-156
lines changed

5 files changed

+41
-156
lines changed

assets/l10n/app_en.arb

+8
Original file line numberDiff line numberDiff line change
@@ -533,5 +533,13 @@
533533
"manyPeopleTyping": "Several people are typing…",
534534
"@manyPeopleTyping": {
535535
"description": "Text to display when there are multiple users typing."
536+
},
537+
"messageIsEditedLabel": "EDITED",
538+
"@messageIsEditedLabel": {
539+
"description": "Label for an edited message. (Use ALL CAPS for cased alphabets: Latin, Greek, Cyrillic, etc.)"
540+
},
541+
"messageIsMovedLabel": "MOVED",
542+
"@messageIsMovedLabel": {
543+
"description": "Label for a moved message. (Use ALL CAPS for cased alphabets: Latin, Greek, Cyrillic, etc.)"
536544
}
537545
}

lib/widgets/edit_state_marker.dart

-74
This file was deleted.

lib/widgets/message_list.dart

+23-12
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import 'page.dart';
2222
import 'profile.dart';
2323
import 'sticky_header.dart';
2424
import 'store.dart';
25-
import 'edit_state_marker.dart';
2625
import 'text.dart';
2726
import 'theme.dart';
2827

@@ -33,7 +32,6 @@ class MessageListTheme extends ThemeExtension<MessageListTheme> {
3332
dateSeparator: Colors.black,
3433
dateSeparatorText: const HSLColor.fromAHSL(0.75, 0, 0, 0.15).toColor(),
3534
dmRecipientHeaderBg: const HSLColor.fromAHSL(1, 46, 0.35, 0.93).toColor(),
36-
editedMovedMarkerCollapsed: const Color.fromARGB(128, 146, 167, 182),
3735
messageTimestamp: const HSLColor.fromAHSL(0.8, 0, 0, 0.2).toColor(),
3836
recipientHeaderText: const HSLColor.fromAHSL(1, 0, 0, 0.15).toColor(),
3937
senderBotIcon: const HSLColor.fromAHSL(1, 180, 0.08, 0.65).toColor(),
@@ -60,8 +58,6 @@ class MessageListTheme extends ThemeExtension<MessageListTheme> {
6058
dateSeparator: Colors.white,
6159
dateSeparatorText: const HSLColor.fromAHSL(0.75, 0, 0, 1).toColor(),
6260
dmRecipientHeaderBg: const HSLColor.fromAHSL(1, 46, 0.15, 0.2).toColor(),
63-
// TODO(design-dark) need proper dark-theme color (this is ad hoc)
64-
editedMovedMarkerCollapsed: const Color.fromARGB(128, 214, 202, 194),
6561
messageTimestamp: const HSLColor.fromAHSL(0.6, 0, 0, 1).toColor(),
6662
recipientHeaderText: const HSLColor.fromAHSL(0.8, 0, 0, 1).toColor(),
6763
senderBotIcon: const HSLColor.fromAHSL(1, 180, 0.05, 0.5).toColor(),
@@ -86,7 +82,6 @@ class MessageListTheme extends ThemeExtension<MessageListTheme> {
8682
required this.dateSeparator,
8783
required this.dateSeparatorText,
8884
required this.dmRecipientHeaderBg,
89-
required this.editedMovedMarkerCollapsed,
9085
required this.messageTimestamp,
9186
required this.recipientHeaderText,
9287
required this.senderBotIcon,
@@ -111,7 +106,6 @@ class MessageListTheme extends ThemeExtension<MessageListTheme> {
111106
final Color dateSeparator;
112107
final Color dateSeparatorText;
113108
final Color dmRecipientHeaderBg;
114-
final Color editedMovedMarkerCollapsed;
115109
final Color messageTimestamp;
116110
final Color recipientHeaderText;
117111
final Color senderBotIcon;
@@ -127,7 +121,6 @@ class MessageListTheme extends ThemeExtension<MessageListTheme> {
127121
Color? dateSeparator,
128122
Color? dateSeparatorText,
129123
Color? dmRecipientHeaderBg,
130-
Color? editedMovedMarkerCollapsed,
131124
Color? messageTimestamp,
132125
Color? recipientHeaderText,
133126
Color? senderBotIcon,
@@ -142,7 +135,6 @@ class MessageListTheme extends ThemeExtension<MessageListTheme> {
142135
dateSeparator: dateSeparator ?? this.dateSeparator,
143136
dateSeparatorText: dateSeparatorText ?? this.dateSeparatorText,
144137
dmRecipientHeaderBg: dmRecipientHeaderBg ?? this.dmRecipientHeaderBg,
145-
editedMovedMarkerCollapsed: editedMovedMarkerCollapsed ?? this.editedMovedMarkerCollapsed,
146138
messageTimestamp: messageTimestamp ?? this.messageTimestamp,
147139
recipientHeaderText: recipientHeaderText ?? this.recipientHeaderText,
148140
senderBotIcon: senderBotIcon ?? this.senderBotIcon,
@@ -164,7 +156,6 @@ class MessageListTheme extends ThemeExtension<MessageListTheme> {
164156
dateSeparator: Color.lerp(dateSeparator, other.dateSeparator, t)!,
165157
dateSeparatorText: Color.lerp(dateSeparatorText, other.dateSeparatorText, t)!,
166158
dmRecipientHeaderBg: Color.lerp(streamMessageBgDefault, other.dmRecipientHeaderBg, t)!,
167-
editedMovedMarkerCollapsed: Color.lerp(editedMovedMarkerCollapsed, other.editedMovedMarkerCollapsed, t)!,
168159
messageTimestamp: Color.lerp(messageTimestamp, other.messageTimestamp, t)!,
169160
recipientHeaderText: Color.lerp(recipientHeaderText, other.recipientHeaderText, t)!,
170161
senderBotIcon: Color.lerp(senderBotIcon, other.senderBotIcon, t)!,
@@ -1256,6 +1247,16 @@ class MessageWithPossibleSender extends StatelessWidget {
12561247
]);
12571248
}
12581249

1250+
final localizations = ZulipLocalizations.of(context);
1251+
String? editStateText;
1252+
switch (message.editState) {
1253+
case MessageEditState.edited:
1254+
editStateText = localizations.messageIsEditedLabel;
1255+
case MessageEditState.moved:
1256+
editStateText = localizations.messageIsMovedLabel;
1257+
case MessageEditState.none:
1258+
}
1259+
12591260
return GestureDetector(
12601261
behavior: HitTestBehavior.translucent,
12611262
onLongPress: () => showMessageActionSheet(context: context, message: message),
@@ -1265,15 +1266,25 @@ class MessageWithPossibleSender extends StatelessWidget {
12651266
if (senderRow != null)
12661267
Padding(padding: const EdgeInsets.fromLTRB(16, 2, 16, 0),
12671268
child: senderRow),
1268-
EditStateMarker(
1269-
editState: message.editState,
1269+
Row(
1270+
crossAxisAlignment: CrossAxisAlignment.baseline,
1271+
textBaseline: localizedTextBaseline(context),
12701272
children: [
1273+
const SizedBox(width: 16),
12711274
Expanded(child: Column(
12721275
crossAxisAlignment: CrossAxisAlignment.stretch,
12731276
children: [
12741277
MessageContent(message: message, content: item.content),
12751278
if ((message.reactions?.total ?? 0) > 0)
1276-
ReactionChipsList(messageId: message.id, reactions: message.reactions!)
1279+
ReactionChipsList(messageId: message.id, reactions: message.reactions!),
1280+
if (editStateText != null)
1281+
Text(editStateText, textAlign: TextAlign.end,
1282+
style: TextStyle(
1283+
color: designVariables.labelEdited,
1284+
fontSize: 12,
1285+
height: (12 / 12),
1286+
letterSpacing: proportionalLetterSpacing(
1287+
context, 0.05, baseFontSize: 12))),
12771288
])),
12781289
SizedBox(width: 16,
12791290
child: message.flags.contains(MessageFlag.starred)

lib/widgets/theme.dart

+7
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
112112
borderBar: const Color(0x33000000),
113113
icon: const Color(0xff666699),
114114
labelCounterUnread: const Color(0xff222222),
115+
labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 0).toColor(),
115116
labelMenuButton: const Color(0xff222222),
116117
mainBackground: const Color(0xfff0f0f0),
117118
title: const Color(0xff1a1a1a),
@@ -140,6 +141,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
140141
borderBar: Colors.black.withOpacity(0.41),
141142
icon: const Color(0xff7070c2),
142143
labelCounterUnread: const Color(0xffffffff).withOpacity(0.7),
144+
labelEdited: const HSLColor.fromAHSL(0.35, 0, 0, 1).toColor(),
143145
labelMenuButton: const Color(0xffffffff).withOpacity(0.85),
144146
mainBackground: const Color(0xff1d1d1d),
145147
title: const Color(0xffffffff),
@@ -174,6 +176,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
174176
required this.borderBar,
175177
required this.icon,
176178
required this.labelCounterUnread,
179+
required this.labelEdited,
177180
required this.labelMenuButton,
178181
required this.mainBackground,
179182
required this.title,
@@ -210,6 +213,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
210213
final Color borderBar;
211214
final Color icon;
212215
final Color labelCounterUnread;
216+
final Color labelEdited;
213217
final Color labelMenuButton;
214218
final Color mainBackground;
215219
final Color title;
@@ -241,6 +245,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
241245
Color? borderBar,
242246
Color? icon,
243247
Color? labelCounterUnread,
248+
Color? labelEdited,
244249
Color? labelMenuButton,
245250
Color? mainBackground,
246251
Color? title,
@@ -267,6 +272,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
267272
borderBar: borderBar ?? this.borderBar,
268273
icon: icon ?? this.icon,
269274
labelCounterUnread: labelCounterUnread ?? this.labelCounterUnread,
275+
labelEdited: labelEdited ?? this.labelEdited,
270276
labelMenuButton: labelMenuButton ?? this.labelMenuButton,
271277
mainBackground: mainBackground ?? this.mainBackground,
272278
title: title ?? this.title,
@@ -300,6 +306,7 @@ class DesignVariables extends ThemeExtension<DesignVariables> {
300306
borderBar: Color.lerp(borderBar, other.borderBar, t)!,
301307
icon: Color.lerp(icon, other.icon, t)!,
302308
labelCounterUnread: Color.lerp(labelCounterUnread, other.labelCounterUnread, t)!,
309+
labelEdited: Color.lerp(labelEdited, other.labelEdited, t)!,
303310
labelMenuButton: Color.lerp(labelMenuButton, other.labelMenuButton, t)!,
304311
mainBackground: Color.lerp(mainBackground, other.mainBackground, t)!,
305312
title: Color.lerp(title, other.title, t)!,

test/widgets/message_list_test.dart

+3-70
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import 'package:zulip/model/narrow.dart';
1616
import 'package:zulip/model/store.dart';
1717
import 'package:zulip/widgets/autocomplete.dart';
1818
import 'package:zulip/widgets/content.dart';
19-
import 'package:zulip/widgets/emoji_reaction.dart';
2019
import 'package:zulip/widgets/icons.dart';
2120
import 'package:zulip/widgets/message_list.dart';
2221
import 'package:zulip/widgets/store.dart';
@@ -1038,10 +1037,10 @@ void main() {
10381037
});
10391038
});
10401039

1041-
group('EditStateMarker', () {
1040+
group('edit state label', () {
10421041
void checkMarkersCount({required int edited, required int moved}) {
1043-
check(find.byIcon(ZulipIcons.edited).evaluate()).length.equals(edited);
1044-
check(find.byIcon(ZulipIcons.message_moved).evaluate()).length.equals(moved);
1042+
check(find.text('EDITED').evaluate()).length.equals(edited);
1043+
check(find.text('MOVED').evaluate()).length.equals(moved);
10451044
}
10461045

10471046
testWidgets('no edited or moved messages', (tester) async {
@@ -1069,72 +1068,6 @@ void main() {
10691068
await tester.pump();
10701069
checkMarkersCount(edited: 2, moved: 0);
10711070
});
1072-
1073-
List<List<(String, Rect)>> captureMessageRects(
1074-
WidgetTester tester,
1075-
List<Message> messages,
1076-
Message targetMessage,
1077-
) {
1078-
assert(messages.contains(targetMessage));
1079-
final List<List<(String, Rect)>> result = [];
1080-
for (final message in messages) {
1081-
final baseFinder = find.byWidgetPredicate(
1082-
(widget) => widget is MessageWithPossibleSender
1083-
&& widget.item.message.id == message.id);
1084-
1085-
Rect getRectMatching(Finder matching) {
1086-
final finder = find.descendant(
1087-
of: baseFinder, matching: matching)..tryEvaluate();
1088-
check(finder.found, because: '${message.content}: $matching')
1089-
.length.equals(1);
1090-
return tester.getRect(finder.first);
1091-
}
1092-
1093-
result.add([
1094-
('MessageWithPossibleSender', tester.getRect(baseFinder)),
1095-
('MessageContent', getRectMatching(find.byType(MessageContent))),
1096-
('Paragraph', getRectMatching(find.byType(Paragraph))),
1097-
if (message.id == targetMessage.id) ...[
1098-
('Star', getRectMatching(find.byIcon(ZulipIcons.star_filled))),
1099-
('ReactionChipsList', getRectMatching(find.byType(ReactionChipsList))),
1100-
],
1101-
]);
1102-
}
1103-
return result;
1104-
}
1105-
1106-
testWidgets('edit state updates do not affect layout', (tester) async {
1107-
final messages = [
1108-
eg.streamMessage(topic: 'orig'),
1109-
eg.streamMessage(
1110-
topic: 'orig',
1111-
reactions: [eg.unicodeEmojiReaction, eg.realmEmojiReaction],
1112-
flags: [MessageFlag.starred]),
1113-
eg.streamMessage(topic: 'orig'),
1114-
];
1115-
final StreamMessage messageWithMarker = messages[1];
1116-
await setupMessageListPage(tester, messages: messages);
1117-
final rectsBefore = captureMessageRects(tester, messages, messageWithMarker);
1118-
checkMarkersCount(edited: 0, moved: 0);
1119-
1120-
await store.handleEvent(eg.updateMessageEventMoveFrom(
1121-
origMessages: [store.messages[messageWithMarker.id] as StreamMessage],
1122-
newTopic: 'new'));
1123-
await tester.pump();
1124-
await store.handleEvent(eg.updateMessageEventMoveFrom(
1125-
origMessages: [store.messages[messageWithMarker.id] as StreamMessage],
1126-
newTopic: 'orig'));
1127-
await tester.pump();
1128-
check(captureMessageRects(tester, messages, messageWithMarker))
1129-
.deepEquals(rectsBefore);
1130-
checkMarkersCount(edited: 0, moved: 1);
1131-
1132-
await store.handleEvent(eg.updateMessageEditEvent(messageWithMarker));
1133-
await tester.pump();
1134-
check(captureMessageRects(tester, messages, messageWithMarker))
1135-
.deepEquals(rectsBefore);
1136-
checkMarkersCount(edited: 1, moved: 0);
1137-
});
11381071
});
11391072

11401073
group('_UnreadMarker animations', () {

0 commit comments

Comments
 (0)