Repository: zulip/zulip-flutter (language: dart)

Issue:
Extra recipient header when topic changes case
If you visit the thread `#translation > Missing string` on chat.zulip.org, around this message (the first one in today's thread):
https://chat.zulip.org/#narrow/stream/58-translation/topic/Missing.20string/near/1828362

you'll find it gets its own recipient header, even in a topic narrow, even though there are previous messages. Specifically there's a recipient header "missing string" for the older messages (which are from 2021), and another one "Missing string" for that message and the ones after it.

In general Zulip topics are case-insensitive. Specifically, if you look at that conversation in Zulip web, there's just one recipient header; it reflects the older messages, so "missing string". So we should do the same.

Related issues:
* #980 
* #1205


Discussion hints:
We had a second report of the issue today, with a screenshot:
https://chat.zulip.org/#narrow/stream/48-mobile/topic/Case.20sensitivity.20in.20topics/near/1954248


Pull request: Make topic headers case-insensitive
Topics in Zulip are case-insensitive. This change makes the message list's topic headers match that behavior, so messages whose topics differ only in case (like "missing string" and "Missing string") share a single header.  This brings the behavior in line with Zulip web.

### What is the change?

The change modifies the topic comparison logic in `haveSameRecipient()` to use case-insensitive comparison when determining whether to show a new recipient header.

### Screenshots

| Before                                                                                       | After                                                                                        |
|---------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
| <img src="https://github.com/user-attachments/assets/fdc22a9b-bf28-42ec-aa74-0ea94dde20ff" width="400"/> | <img src="https://github.com/user-attachments/assets/7732218a-d42f-4421-8a30-d89a85e60389" width="400"/> |




Fixes #739

Test command: flutter test test/model/message_list_test.dart --reporter expanded 2>&1

Fix patch:
diff --git a/lib/model/message_list.dart b/lib/model/message_list.dart
index 440d12f2b8..ecfb16c2b9 100644
--- a/lib/model/message_list.dart
+++ b/lib/model/message_list.dart
@@ -352,7 +352,7 @@ mixin _MessageSequence {
 bool haveSameRecipient(Message prevMessage, Message message) {
   if (prevMessage is StreamMessage && message is StreamMessage) {
     if (prevMessage.streamId != message.streamId) return false;
-    if (prevMessage.topic != message.topic) return false;
+    if (prevMessage.topic.toLowerCase() != message.topic.toLowerCase()) return false;
   } else if (prevMessage is DmMessage && message is DmMessage) {
     if (!_equalIdSequences(prevMessage.allRecipientIds, message.allRecipientIds)) {
       return false;


Test patch:
diff --git a/test/model/message_list_test.dart b/test/model/message_list_test.dart
index e7b6cd7834..e92b2489b3 100644
--- a/test/model/message_list_test.dart
+++ b/test/model/message_list_test.dart
@@ -1803,6 +1803,26 @@ void main() {
         }
       }
     });
+
+    group('topics compared case-insensitively', () {
+      void doTest(String description, String topicA, String topicB, bool expected) {
+        test(description, () {
+          final stream = eg.stream();
+          final messageA = eg.streamMessage(stream: stream, topic: topicA);
+          final messageB = eg.streamMessage(stream: stream, topic: topicB);
+          check(haveSameRecipient(messageA, messageB)).equals(expected);
+        });
+      }
+
+      doTest('same case, all lower',               'abc',  'abc',  true);
+      doTest('same case, all upper',               'ABC',  'ABC',  true);
+      doTest('same case, mixed',                   'AbC',  'AbC',  true);
+      doTest('same non-cased chars',               '嗎',    '嗎',    true);
+      doTest('different case',                     'aBc',  'ABC',  true);
+      doTest('different case, same diacritics',    'AbÇ',  'aBç',  true);
+      doTest('same letters, different diacritics', 'ma',   'mǎ',   false);
+      doTest('having different CJK characters',    '嗎', '馬', false);
+    });
   });
 
   test('messagesSameDay', () {