@@ -22,6 +22,7 @@ import androidx.paging.PagingData
2222import androidx.paging.map
2323import kotlinx.coroutines.flow.Flow
2424import kotlinx.coroutines.flow.flatMapLatest
25+ import kotlinx.coroutines.flow.flowOf
2526import kotlinx.coroutines.flow.map
2627import kotlinx.coroutines.flow.mapLatest
2728import kotlinx.coroutines.withContext
@@ -139,6 +140,7 @@ class PacketRepositoryImpl(private val dbManager: DatabaseProvider, private val
139140 rssi = packet.rssi,
140141 hopsAway = packet.hopsAway,
141142 filtered = filtered,
143+ messageText = packet.text.orEmpty(),
142144 )
143145 insertRoomPacket(packetToSave)
144146 }
@@ -278,6 +280,7 @@ class PacketRepositoryImpl(private val dbManager: DatabaseProvider, private val
278280 rssi = packet.rssi,
279281 hopsAway = packet.hopsAway,
280282 filtered = filtered,
283+ messageText = packet.text.orEmpty(),
281284 )
282285 insertRoomPacket(packetToSave)
283286 }
@@ -510,6 +513,54 @@ class PacketRepositoryImpl(private val dbManager: DatabaseProvider, private val
510513 sfpp_hash = sfppHash,
511514 )
512515
516+ override fun searchMessages (query : String , contactKey : String? , getNode : (String? ) -> Node ): Flow <List <Message >> {
517+ val sanitized = sanitizeFtsQuery(query)
518+ if (sanitized.isBlank()) return flowOf(emptyList())
519+ return dbManager.currentDb.flatMapLatest { db ->
520+ kotlinx.coroutines.flow.flow {
521+ val dao = db.packetDao()
522+ val packets =
523+ if (contactKey != null ) {
524+ dao.searchMessagesInConversation(sanitized, contactKey)
525+ } else {
526+ dao.searchMessages(sanitized)
527+ }
528+ emit(
529+ packets.map { packet ->
530+ val node = getNode(packet.data.from)
531+ val isFromLocal =
532+ node.user.id == DataPacket .ID_LOCAL ||
533+ (packet.myNodeNum != 0 && node.num == packet.myNodeNum)
534+ Message (
535+ uuid = packet.uuid,
536+ receivedTime = packet.received_time,
537+ node = node,
538+ text = packet.data.text.orEmpty(),
539+ fromLocal = isFromLocal,
540+ time = org.meshtastic.core.model.util.getShortDateTime(packet.data.time),
541+ snr = packet.snr,
542+ rssi = packet.rssi,
543+ hopsAway = packet.hopsAway,
544+ read = packet.read,
545+ status = packet.data.status,
546+ routingError = packet.routingError,
547+ packetId = packet.packetId,
548+ emojis = emptyList(),
549+ replyId = packet.data.replyId,
550+ )
551+ },
552+ )
553+ }
554+ }
555+ }
556+
557+ /* *
558+ * Sanitizes a user query for FTS5 by wrapping each token in double quotes. This escapes FTS5 special characters (*,
559+ * -, NEAR, etc.) while still allowing multi-word searches as implicit AND queries.
560+ */
561+ private fun sanitizeFtsQuery (query : String ): String =
562+ query.split(" \\ s+" .toRegex()).filter { it.isNotBlank() }.joinToString(" " ) { " \" ${it.replace(" \" " , " " )} \" " }
563+
513564 companion object {
514565 private const val CONTACTS_PAGE_SIZE = 30
515566 private const val MESSAGES_PAGE_SIZE = 50
0 commit comments