Skip to content

Commit

Permalink
home: Sort leading emoji first in channel names
Browse files Browse the repository at this point in the history
Update the channel sorting logic to ensure streams with leading emojis
in their names are listed above those without emojis. The updated
sorting respects pinned, muted, and unmuted streams while handling
emoji precedence and maintaining alphabetical order for ties.

Fixes: zulip#1202
  • Loading branch information
lakshya1goel committed Dec 29, 2024
1 parent 3ff7096 commit 6e0e1f0
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 1 deletion.
39 changes: 38 additions & 1 deletion lib/widgets/subscription_list.dart
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,30 @@ class _SubscriptionList extends StatelessWidget {

@override
Widget build(BuildContext context) {

final sortedSubscriptions = List<Subscription>.from(subscriptions)
..sort((a, b) {
if (a.pinToTop != b.pinToTop) {
return a.pinToTop ? -1 : 1;
}

if (a.isMuted != b.isMuted) {
return a.isMuted ? 1 : -1;
}

final isEmojiA = _startsWithEmoji(a.name);
final isEmojiB = _startsWithEmoji(b.name);
if (isEmojiA != isEmojiB) {
return isEmojiA ? -1 : 1;
}

return a.name.toLowerCase().compareTo(b.name.toLowerCase());
});

return SliverList.builder(
itemCount: subscriptions.length,
itemBuilder: (BuildContext context, int index) {
final subscription = subscriptions[index];
final subscription = sortedSubscriptions[index];
final unreadCount = unreadsModel!.countInChannel(subscription.streamId);
final showMutedUnreadBadge = unreadCount == 0
&& unreadsModel!.countInChannelNarrow(subscription.streamId) > 0;
Expand All @@ -189,6 +209,23 @@ class _SubscriptionList extends StatelessWidget {
}
}

bool _startsWithEmoji(String name) {
final firstChar = name.characters.first;
final int firstCharCode = firstChar.runes.first;

return (firstCharCode >= 0x1F600 && firstCharCode <= 0x1F64F) || // Emoticons
(firstCharCode >= 0x1F300 && firstCharCode <= 0x1F5FF) || // Misc Symbols and Pictographs
(firstCharCode >= 0x1F680 && firstCharCode <= 0x1F6FF) || // Transport and Map
(firstCharCode >= 0x1F700 && firstCharCode <= 0x1F77F) || // Alchemical Symbols
(firstCharCode >= 0x2600 && firstCharCode <= 0x26FF) || // Misc Symbols
(firstCharCode >= 0x2700 && firstCharCode <= 0x27BF) || // Dingbats
(firstCharCode >= 0xFE00 && firstCharCode <= 0xFE0F) || // Variation Selectors
(firstCharCode >= 0x1F900 && firstCharCode <= 0x1F9FF) || // Supplemental Symbols and Pictographs
(firstCharCode >= 0x1FA70 && firstCharCode <= 0x1FAFF) || // Symbols and Pictographs Extended-A
(firstCharCode >= 0x1F1E6 && firstCharCode <= 0x1F1FF) || // Flags
(firstCharCode >= 0x1F000 && firstCharCode <= 0x1FFFF); // Supplementary Multilingual Plane
}

@visibleForTesting
class SubscriptionItem extends StatelessWidget {
const SubscriptionItem({
Expand Down
26 changes: 26 additions & 0 deletions test/widgets/subscription_list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,32 @@ void main() {
]);
check(listedStreamIds(tester)).deepEquals([2, 1, 3, 4, 6, 5]);
});

testWidgets('channels with emoji in name are listed above non-emoji names', (tester) async {
await setupStreamListPage(tester, subscriptions: [
eg.subscription(eg.stream(streamId: 1, name: '😊 Happy Stream')),
eg.subscription(eg.stream(streamId: 2, name: 'Alpha Stream')),
eg.subscription(eg.stream(streamId: 3, name: '🚀 Rocket Stream')),
eg.subscription(eg.stream(streamId: 4, name: 'Beta Stream')),
]);

check(listedStreamIds(tester)).deepEquals([1, 3, 2, 4]);
});

testWidgets('channels with emoji in name, pinned, unpinned, muted, and unmuted are sorted correctly', (tester) async {
await setupStreamListPage(tester, subscriptions: [
eg.subscription(eg.stream(streamId: 1, name: '😊 Happy Stream'), pinToTop: true, isMuted: false),
eg.subscription(eg.stream(streamId: 2, name: '🚀 Rocket Stream'), pinToTop: true, isMuted: true),
eg.subscription(eg.stream(streamId: 3, name: 'Alpha Stream'), pinToTop: true, isMuted: false),
eg.subscription(eg.stream(streamId: 4, name: 'Beta Stream'), pinToTop: true, isMuted: true),
eg.subscription(eg.stream(streamId: 5, name: '🌟 Star Stream'), pinToTop: false, isMuted: false),
eg.subscription(eg.stream(streamId: 6, name: '🔥 Fire Stream'), pinToTop: false, isMuted: true),
eg.subscription(eg.stream(streamId: 7, name: 'Gamma Stream'), pinToTop: false, isMuted: false),
eg.subscription(eg.stream(streamId: 8, name: 'Delta Stream'), pinToTop: false, isMuted: true),
]);

check(listedStreamIds(tester)).deepEquals([1,3,2,4,5,7,6,8]);
});
});

testWidgets('unread badge shows with unreads', (tester) async {
Expand Down

0 comments on commit 6e0e1f0

Please sign in to comment.