@@ -41,6 +41,7 @@ import org.prauga.messages.model.Conversation
4141import org.prauga.messages.model.Message
4242import org.prauga.messages.model.Recipient
4343import org.prauga.messages.model.SearchResult
44+ import org.prauga.messages.model.SearchItem
4445import org.prauga.messages.util.PhoneNumberUtils
4546import org.prauga.messages.util.tryOrNull
4647import java.util.concurrent.TimeUnit
@@ -207,6 +208,77 @@ class ConversationRepositoryImpl @Inject constructor(
207208 return conversationMatches + messagesByConversation
208209 }
209210
211+ override fun searchConversationsGrouped (query : CharSequence ): List <SearchItem > {
212+ val realm = Realm .getDefaultInstance()
213+
214+ val normalizedQuery = query.removeAccents()
215+ val conversations = realm.copyFromRealm(
216+ realm
217+ .where(Conversation ::class .java)
218+ .notEqualTo(" id" , 0L )
219+ .isNotNull(" lastMessage" )
220+ .equalTo(" blocked" , false )
221+ .isNotEmpty(" recipients" )
222+ .sort(" pinned" , Sort .DESCENDING , " lastMessage.date" , Sort .DESCENDING )
223+ .findAll()
224+ )
225+
226+ val conversationsById = conversations.associateBy { it.id }
227+
228+ // Get all messages matching the query, grouped by conversation
229+ val messagesByConversation = realm.copyFromRealm(
230+ realm
231+ .where(Message ::class .java)
232+ .beginGroup()
233+ .contains(" body" , normalizedQuery, Case .INSENSITIVE )
234+ .or ()
235+ .contains(" parts.text" , normalizedQuery, Case .INSENSITIVE )
236+ .endGroup()
237+ .sort(" date" , Sort .DESCENDING )
238+ .findAll()
239+ )
240+ .groupBy { message -> message.threadId }
241+ .mapNotNull { (threadId, messages) ->
242+ conversationsById[threadId]?.let { conversation ->
243+ Pair (conversation, messages)
244+ }
245+ }
246+ .sortedByDescending { (_, messages) -> messages.size }
247+
248+ realm.close()
249+
250+ // Build the flattened list with headers and messages
251+ val result = mutableListOf<SearchItem >()
252+
253+ messagesByConversation.forEach { (conversation, messages) ->
254+ // Add conversation header
255+ result.add(SearchItem .Header (
256+ conversationId = conversation.id,
257+ title = conversation.getTitle()
258+ ))
259+
260+ // Add matching messages
261+ messages.forEach { message ->
262+ val body = when {
263+ message.body.isNotEmpty() -> message.body
264+ message.parts.isNotEmpty() -> message.parts.firstOrNull()?.text ? : " "
265+ else -> " "
266+ }
267+
268+ if (body.isNotEmpty()) {
269+ result.add(SearchItem .Message (
270+ messageId = message.id,
271+ conversationId = conversation.id,
272+ body = body,
273+ timestamp = message.date
274+ ))
275+ }
276+ }
277+ }
278+
279+ return result
280+ }
281+
210282 override fun getBlockedConversations (): RealmResults <Conversation > =
211283 Realm .getDefaultInstance()
212284 .where(Conversation ::class .java)
0 commit comments