Skip to content

Commit a922f56

Browse files
committed
msglist: Tapping a message in Starred or Mentions opens anchored msglist
Fixes #1621.
1 parent 0cf88f3 commit a922f56

File tree

3 files changed

+111
-3
lines changed

3 files changed

+111
-3
lines changed

lib/widgets/message_list.dart

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:collection/collection.dart';
24
import 'package:flutter/material.dart';
35
import 'package:flutter_color_models/flutter_color_models.dart';
@@ -989,11 +991,15 @@ class _MessageListState extends State<MessageList> with PerAccountStoreAwareStat
989991
final header = RecipientHeader(message: data.message, narrow: widget.narrow);
990992
return MessageItem(
991993
key: ValueKey(data.message.id),
994+
narrow: widget.narrow,
992995
header: header,
993996
item: data);
994997
case MessageListOutboxMessageItem():
995998
final header = RecipientHeader(message: data.message, narrow: widget.narrow);
996-
return MessageItem(header: header, item: data);
999+
return MessageItem(
1000+
narrow: widget.narrow,
1001+
header: header,
1002+
item: data);
9971003
}
9981004
}
9991005
}
@@ -1315,10 +1321,12 @@ class DateSeparator extends StatelessWidget {
13151321
class MessageItem extends StatelessWidget {
13161322
const MessageItem({
13171323
super.key,
1324+
required this.narrow,
13181325
required this.item,
13191326
required this.header,
13201327
});
13211328

1329+
final Narrow narrow;
13221330
final MessageListMessageBaseItem item;
13231331
final Widget header;
13241332

@@ -1331,7 +1339,9 @@ class MessageItem extends StatelessWidget {
13311339
color: designVariables.bgMessageRegular,
13321340
child: Column(children: [
13331341
switch (item) {
1334-
MessageListMessageItem() => MessageWithPossibleSender(item: item),
1342+
MessageListMessageItem() => MessageWithPossibleSender(
1343+
narrow: narrow,
1344+
item: item),
13351345
MessageListOutboxMessageItem() => OutboxMessageWithPossibleSender(item: item),
13361346
},
13371347
// TODO refine this padding; discussion:
@@ -1748,8 +1758,13 @@ class _SenderRow extends StatelessWidget {
17481758
// - https://github.com/zulip/zulip-mobile/issues/5511
17491759
// - https://www.figma.com/file/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=538%3A20849&mode=dev
17501760
class MessageWithPossibleSender extends StatelessWidget {
1751-
const MessageWithPossibleSender({super.key, required this.item});
1761+
const MessageWithPossibleSender({
1762+
super.key,
1763+
required this.narrow,
1764+
required this.item,
1765+
});
17521766

1767+
final Narrow narrow;
17531768
final MessageListMessageItem item;
17541769

17551770
@override
@@ -1798,8 +1813,24 @@ class MessageWithPossibleSender extends StatelessWidget {
17981813
}
17991814
}
18001815

1816+
final tapOpensConversation = switch (narrow) {
1817+
CombinedFeedNarrow()
1818+
|| ChannelNarrow()
1819+
|| TopicNarrow()
1820+
|| DmNarrow() => false,
1821+
MentionsNarrow()
1822+
|| StarredMessagesNarrow() => true,
1823+
};
1824+
18011825
return GestureDetector(
18021826
behavior: HitTestBehavior.translucent,
1827+
onTap: tapOpensConversation
1828+
? () => unawaited(Navigator.push(context,
1829+
MessageListPage.buildRoute(context: context,
1830+
narrow: SendableNarrow.ofMessage(message, selfUserId: store.selfUserId),
1831+
// TODO(#1655) "this view does not mark messages as read on scroll"
1832+
initAnchorMessageId: message.id)))
1833+
: null,
18031834
onLongPress: () => showMessageActionSheet(context: context, message: message),
18041835
child: Padding(
18051836
padding: const EdgeInsets.only(top: 4),

test/widgets/message_list_checks.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ import 'package:zulip/widgets/message_list.dart';
44

55
extension MessageListPageChecks on Subject<MessageListPage> {
66
Subject<Narrow> get initNarrow => has((x) => x.initNarrow, 'initNarrow');
7+
Subject<int?> get initAnchorMessageId => has((x) => x.initAnchorMessageId, 'initAnchorMessageId');
78
}

test/widgets/message_list_test.dart

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,82 @@ void main() {
16531653

16541654
debugNetworkImageHttpClientProvider = null;
16551655
});
1656+
1657+
group('Opens conversation on tap?', () {
1658+
// (copied from test/widgets/content_test.dart)
1659+
Future<void> tapText(WidgetTester tester, Finder textFinder) async {
1660+
final height = tester.getSize(textFinder).height;
1661+
final target = tester.getTopLeft(textFinder)
1662+
.translate(height/4, height/2); // aim for middle of first letter
1663+
await tester.tapAt(target);
1664+
}
1665+
1666+
final subscription = eg.subscription(eg.stream(streamId: eg.defaultStreamMessageStreamId));
1667+
final topic = 'some topic';
1668+
1669+
void doTest(Narrow narrow, {
1670+
required bool expected,
1671+
required Message Function() mkMessage,
1672+
}) {
1673+
testWidgets('${expected ? 'yes' : 'no'}, if in $narrow', (tester) async {
1674+
final message = mkMessage();
1675+
1676+
Route<dynamic>? lastPushedRoute;
1677+
final navObserver = TestNavigatorObserver()
1678+
..onPushed = ((route, prevRoute) => lastPushedRoute = route);
1679+
1680+
await setupMessageListPage(
1681+
tester,
1682+
narrow: narrow,
1683+
messages: [message],
1684+
subscriptions: [subscription],
1685+
navObservers: [navObserver]
1686+
);
1687+
lastPushedRoute = null;
1688+
1689+
// Tapping interactive content still works.
1690+
await store.handleEvent(eg.updateMessageEditEvent(message,
1691+
renderedContent: '<p><a href="https://example/">link</a></p>'));
1692+
await tester.pump();
1693+
await tapText(tester, find.text('link'));
1694+
await tester.pump(Duration.zero);
1695+
check(lastPushedRoute).isNull();
1696+
final launchUrlCalls = testBinding.takeLaunchUrlCalls();
1697+
check(launchUrlCalls.single.url).equals(Uri.parse('https://example/'));
1698+
1699+
// Tapping non-interactive content opens the conversation (if expected).
1700+
await store.handleEvent(eg.updateMessageEditEvent(message,
1701+
renderedContent: '<p>plain content</p>'));
1702+
await tester.pump();
1703+
await tapText(tester, find.text('plain content'));
1704+
if (expected) {
1705+
final expectedNarrow = SendableNarrow.ofMessage(message, selfUserId: store.selfUserId);
1706+
1707+
check(lastPushedRoute).isNotNull().isA<MaterialAccountWidgetRoute>()
1708+
.page.isA<MessageListPage>()
1709+
..initNarrow.equals(expectedNarrow)
1710+
..initAnchorMessageId.equals(message.id);
1711+
} else {
1712+
check(lastPushedRoute).isNull();
1713+
}
1714+
1715+
// TODO test tapping whitespace in message
1716+
});
1717+
}
1718+
1719+
doTest(expected: false, CombinedFeedNarrow(),
1720+
mkMessage: () => eg.streamMessage());
1721+
doTest(expected: false, ChannelNarrow(subscription.streamId),
1722+
mkMessage: () => eg.streamMessage(stream: subscription));
1723+
doTest(expected: false, TopicNarrow(subscription.streamId, eg.t(topic)),
1724+
mkMessage: () => eg.streamMessage(stream: subscription));
1725+
doTest(expected: false, DmNarrow.withUsers([], selfUserId: eg.selfUser.userId),
1726+
mkMessage: () => eg.streamMessage(stream: subscription, topic: topic));
1727+
doTest(expected: true, StarredMessagesNarrow(),
1728+
mkMessage: () => eg.streamMessage(flags: [MessageFlag.starred]));
1729+
doTest(expected: true, MentionsNarrow(),
1730+
mkMessage: () => eg.streamMessage(flags: [MessageFlag.mentioned]));
1731+
});
16561732
});
16571733

16581734
group('OutboxMessageWithPossibleSender', () {

0 commit comments

Comments
 (0)