Skip to content

Commit c24de94

Browse files
committed
chats: Make cached message reads thread-aware
1 parent 21ef996 commit c24de94

12 files changed

Lines changed: 232 additions & 50 deletions

File tree

data/src/main/java/org/monogram/data/datasource/cache/ChatLocalDataSource.kt

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,32 @@ interface ChatLocalDataSource {
1818
suspend fun clearAll()
1919

2020
fun getMessagesForChat(chatId: Long): Flow<List<MessageEntity>>
21-
suspend fun getMessagesOlder(chatId: Long, fromMessageId: Long, limit: Int): List<MessageEntity>
22-
suspend fun getMessagesNewer(chatId: Long, fromMessageId: Long, limit: Int): List<MessageEntity>
23-
suspend fun getMessagesAround(chatId: Long, messageId: Long, limit: Int): List<MessageEntity>
24-
suspend fun getLatestMessages(chatId: Long, limit: Int): List<MessageEntity>
21+
suspend fun getMessagesOlder(
22+
chatId: Long,
23+
fromMessageId: Long,
24+
limit: Int,
25+
threadId: Long? = null
26+
): List<MessageEntity>
27+
28+
suspend fun getMessagesNewer(
29+
chatId: Long,
30+
fromMessageId: Long,
31+
limit: Int,
32+
threadId: Long? = null
33+
): List<MessageEntity>
34+
35+
suspend fun getMessagesAround(
36+
chatId: Long,
37+
messageId: Long,
38+
limit: Int,
39+
threadId: Long? = null
40+
): List<MessageEntity>
41+
42+
suspend fun getLatestMessages(
43+
chatId: Long,
44+
limit: Int,
45+
threadId: Long? = null
46+
): List<MessageEntity>
2547
suspend fun getMessagesByIds(chatId: Long, messageIds: List<Long>): List<MessageEntity>
2648
suspend fun insertMessage(message: MessageEntity)
2749
suspend fun insertMessages(messages: List<MessageEntity>)

data/src/main/java/org/monogram/data/datasource/cache/InMemoryChatLocalDataSource.kt

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,25 @@ class InMemoryChatLocalDataSource : ChatLocalDataSource {
7474
messages.getOrPut(chatId) { MutableStateFlow(emptyMap()) }
7575
.map { it.values.sortedByDescending { msg -> msg.date } }
7676

77-
override suspend fun getMessagesOlder(chatId: Long, fromMessageId: Long, limit: Int): List<MessageEntity> {
78-
val chatMessages = messages[chatId]?.value?.values ?: return emptyList()
77+
override suspend fun getMessagesOlder(
78+
chatId: Long,
79+
fromMessageId: Long,
80+
limit: Int,
81+
threadId: Long?
82+
): List<MessageEntity> {
83+
val chatMessages = scopedMessages(chatId, threadId)
7984
return chatMessages.filter { it.id < fromMessageId }
8085
.sortedByDescending { it.date }
8186
.take(limit)
8287
}
8388

84-
override suspend fun getMessagesNewer(chatId: Long, fromMessageId: Long, limit: Int): List<MessageEntity> {
85-
val chatMessages = messages[chatId]?.value?.values ?: return emptyList()
89+
override suspend fun getMessagesNewer(
90+
chatId: Long,
91+
fromMessageId: Long,
92+
limit: Int,
93+
threadId: Long?
94+
): List<MessageEntity> {
95+
val chatMessages = scopedMessages(chatId, threadId)
8696
return chatMessages.filter { it.id > fromMessageId }
8797
.sortedBy { it.date }
8898
.take(limit)
@@ -91,9 +101,10 @@ class InMemoryChatLocalDataSource : ChatLocalDataSource {
91101
override suspend fun getMessagesAround(
92102
chatId: Long,
93103
messageId: Long,
94-
limit: Int
104+
limit: Int,
105+
threadId: Long?
95106
): List<MessageEntity> {
96-
val chatMessages = messages[chatId]?.value?.values ?: return emptyList()
107+
val chatMessages = scopedMessages(chatId, threadId)
97108
val olderLimit = (limit + 1) / 2
98109
val newerLimit = limit - olderLimit
99110
val olderAndTarget = chatMessages.filter { it.id <= messageId }
@@ -106,8 +117,12 @@ class InMemoryChatLocalDataSource : ChatLocalDataSource {
106117
.sortedByDescending { it.date }
107118
}
108119

109-
override suspend fun getLatestMessages(chatId: Long, limit: Int): List<MessageEntity> {
110-
val chatMessages = messages[chatId]?.value?.values ?: return emptyList()
120+
override suspend fun getLatestMessages(
121+
chatId: Long,
122+
limit: Int,
123+
threadId: Long?
124+
): List<MessageEntity> {
125+
val chatMessages = scopedMessages(chatId, threadId)
111126
return chatMessages.sortedByDescending { it.date }.take(limit)
112127
}
113128

@@ -282,4 +297,9 @@ class InMemoryChatLocalDataSource : ChatLocalDataSource {
282297
flow.update { it.filterValues { msg -> msg.createdAt >= timestamp } }
283298
}
284299
}
300+
301+
private fun scopedMessages(chatId: Long, threadId: Long?): List<MessageEntity> {
302+
val chatMessages = messages[chatId]?.value?.values ?: return emptyList()
303+
return chatMessages.filter { message -> message.threadId == threadId }
304+
}
285305
}

data/src/main/java/org/monogram/data/datasource/cache/RoomChatLocalDataSource.kt

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,59 @@ class RoomChatLocalDataSource(
4747

4848
override fun getMessagesForChat(chatId: Long): Flow<List<MessageEntity>> = messageDao.getMessagesForChat(chatId)
4949

50-
override suspend fun getMessagesOlder(chatId: Long, fromMessageId: Long, limit: Int) = messageDao.getMessagesOlder(chatId, fromMessageId, limit)
50+
override suspend fun getMessagesOlder(
51+
chatId: Long,
52+
fromMessageId: Long,
53+
limit: Int,
54+
threadId: Long?
55+
) =
56+
if (threadId == null) {
57+
messageDao.getMainChatMessagesOlder(chatId, fromMessageId, limit)
58+
} else {
59+
messageDao.getThreadMessagesOlder(chatId, threadId, fromMessageId, limit)
60+
}
5161

52-
override suspend fun getMessagesNewer(chatId: Long, fromMessageId: Long, limit: Int) = messageDao.getMessagesNewer(chatId, fromMessageId, limit)
62+
override suspend fun getMessagesNewer(
63+
chatId: Long,
64+
fromMessageId: Long,
65+
limit: Int,
66+
threadId: Long?
67+
) =
68+
if (threadId == null) {
69+
messageDao.getMainChatMessagesNewer(chatId, fromMessageId, limit)
70+
} else {
71+
messageDao.getThreadMessagesNewer(chatId, threadId, fromMessageId, limit)
72+
}
5373

54-
override suspend fun getMessagesAround(chatId: Long, messageId: Long, limit: Int) =
55-
messageDao.getMessagesAround(
56-
chatId = chatId,
57-
messageId = messageId,
58-
olderLimit = (limit + 1) / 2,
59-
newerLimit = limit / 2
60-
)
74+
override suspend fun getMessagesAround(
75+
chatId: Long,
76+
messageId: Long,
77+
limit: Int,
78+
threadId: Long?
79+
) =
80+
if (threadId == null) {
81+
messageDao.getMainChatMessagesAround(
82+
chatId = chatId,
83+
messageId = messageId,
84+
olderLimit = (limit + 1) / 2,
85+
newerLimit = limit / 2
86+
)
87+
} else {
88+
messageDao.getThreadMessagesAround(
89+
chatId = chatId,
90+
threadId = threadId,
91+
messageId = messageId,
92+
olderLimit = (limit + 1) / 2,
93+
newerLimit = limit / 2
94+
)
95+
}
6196

62-
override suspend fun getLatestMessages(chatId: Long, limit: Int) = messageDao.getLatestMessages(chatId, limit)
97+
override suspend fun getLatestMessages(chatId: Long, limit: Int, threadId: Long?) =
98+
if (threadId == null) {
99+
messageDao.getMainChatLatestMessages(chatId, limit)
100+
} else {
101+
messageDao.getThreadLatestMessages(chatId, threadId, limit)
102+
}
63103

64104
override suspend fun getMessagesByIds(chatId: Long, messageIds: List<Long>) =
65105
if (messageIds.isEmpty()) emptyList() else messageDao.getMessagesByIds(chatId, messageIds)

data/src/main/java/org/monogram/data/db/MonogramDatabase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ import org.monogram.data.db.model.WallpaperEntity
6060
SponsorEntity::class,
6161
TextCompositionStyleEntity::class
6262
],
63-
version = 36,
63+
version = 37,
6464
exportSchema = false
6565
)
6666
abstract class MonogramDatabase : RoomDatabase() {

data/src/main/java/org/monogram/data/db/MonogramMigrations.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,17 @@ object MonogramMigrations {
384384
}
385385
}
386386

387+
val MIGRATION_36_37 = object : Migration(36, 37) {
388+
override fun migrate(db: SupportSQLiteDatabase) {
389+
db.execSQL(
390+
"CREATE INDEX IF NOT EXISTS `index_messages_chatId_threadId_date` ON `messages` (`chatId`, `threadId`, `date`)"
391+
)
392+
db.execSQL(
393+
"CREATE INDEX IF NOT EXISTS `index_messages_chatId_threadId_id` ON `messages` (`chatId`, `threadId`, `id`)"
394+
)
395+
}
396+
}
397+
387398
private fun SupportSQLiteDatabase.addColumn(table: String, column: String, definition: String) {
388399
execSQL("ALTER TABLE `$table` ADD COLUMN `$column` $definition")
389400
}

data/src/main/java/org/monogram/data/db/dao/MessageDao.kt

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,41 +12,112 @@ interface MessageDao {
1212
@Query("SELECT * FROM messages WHERE chatId = :chatId ORDER BY date DESC")
1313
fun getMessagesForChat(chatId: Long): Flow<List<MessageEntity>>
1414

15-
@Query("SELECT * FROM messages WHERE chatId = :chatId AND id < :fromMessageId ORDER BY date DESC LIMIT :limit")
16-
suspend fun getMessagesOlder(chatId: Long, fromMessageId: Long, limit: Int): List<MessageEntity>
15+
@Query(
16+
"SELECT * FROM messages WHERE chatId = :chatId AND threadId IS NULL AND id < :fromMessageId ORDER BY date DESC LIMIT :limit"
17+
)
18+
suspend fun getMainChatMessagesOlder(
19+
chatId: Long,
20+
fromMessageId: Long,
21+
limit: Int
22+
): List<MessageEntity>
1723

18-
@Query("SELECT * FROM messages WHERE chatId = :chatId AND id > :fromMessageId ORDER BY date ASC LIMIT :limit")
19-
suspend fun getMessagesNewer(chatId: Long, fromMessageId: Long, limit: Int): List<MessageEntity>
24+
@Query(
25+
"SELECT * FROM messages WHERE chatId = :chatId AND threadId = :threadId AND id < :fromMessageId ORDER BY date DESC LIMIT :limit"
26+
)
27+
suspend fun getThreadMessagesOlder(
28+
chatId: Long,
29+
threadId: Long,
30+
fromMessageId: Long,
31+
limit: Int
32+
): List<MessageEntity>
33+
34+
@Query(
35+
"SELECT * FROM messages WHERE chatId = :chatId AND threadId IS NULL AND id > :fromMessageId ORDER BY date ASC LIMIT :limit"
36+
)
37+
suspend fun getMainChatMessagesNewer(
38+
chatId: Long,
39+
fromMessageId: Long,
40+
limit: Int
41+
): List<MessageEntity>
42+
43+
@Query(
44+
"SELECT * FROM messages WHERE chatId = :chatId AND threadId = :threadId AND id > :fromMessageId ORDER BY date ASC LIMIT :limit"
45+
)
46+
suspend fun getThreadMessagesNewer(
47+
chatId: Long,
48+
threadId: Long,
49+
fromMessageId: Long,
50+
limit: Int
51+
): List<MessageEntity>
2052

2153
@Query(
2254
"""
2355
SELECT * FROM messages
2456
WHERE chatId = :chatId
57+
AND threadId IS NULL
2558
AND id IN (
2659
SELECT id FROM messages
27-
WHERE chatId = :chatId AND id <= :messageId
60+
WHERE chatId = :chatId AND threadId IS NULL AND id <= :messageId
2861
ORDER BY date DESC LIMIT :olderLimit
2962
)
3063
UNION
3164
SELECT * FROM messages
3265
WHERE chatId = :chatId
66+
AND threadId IS NULL
3367
AND id IN (
3468
SELECT id FROM messages
35-
WHERE chatId = :chatId AND id > :messageId
69+
WHERE chatId = :chatId AND threadId IS NULL AND id > :messageId
3670
ORDER BY date ASC LIMIT :newerLimit
3771
)
3872
ORDER BY date DESC
3973
"""
4074
)
41-
suspend fun getMessagesAround(
75+
suspend fun getMainChatMessagesAround(
4276
chatId: Long,
4377
messageId: Long,
4478
olderLimit: Int,
4579
newerLimit: Int
4680
): List<MessageEntity>
4781

48-
@Query("SELECT * FROM messages WHERE chatId = :chatId ORDER BY date DESC LIMIT :limit")
49-
suspend fun getLatestMessages(chatId: Long, limit: Int): List<MessageEntity>
82+
@Query(
83+
"""
84+
SELECT * FROM messages
85+
WHERE chatId = :chatId
86+
AND threadId = :threadId
87+
AND id IN (
88+
SELECT id FROM messages
89+
WHERE chatId = :chatId AND threadId = :threadId AND id <= :messageId
90+
ORDER BY date DESC LIMIT :olderLimit
91+
)
92+
UNION
93+
SELECT * FROM messages
94+
WHERE chatId = :chatId
95+
AND threadId = :threadId
96+
AND id IN (
97+
SELECT id FROM messages
98+
WHERE chatId = :chatId AND threadId = :threadId AND id > :messageId
99+
ORDER BY date ASC LIMIT :newerLimit
100+
)
101+
ORDER BY date DESC
102+
"""
103+
)
104+
suspend fun getThreadMessagesAround(
105+
chatId: Long,
106+
threadId: Long,
107+
messageId: Long,
108+
olderLimit: Int,
109+
newerLimit: Int
110+
): List<MessageEntity>
111+
112+
@Query("SELECT * FROM messages WHERE chatId = :chatId AND threadId IS NULL ORDER BY date DESC LIMIT :limit")
113+
suspend fun getMainChatLatestMessages(chatId: Long, limit: Int): List<MessageEntity>
114+
115+
@Query("SELECT * FROM messages WHERE chatId = :chatId AND threadId = :threadId ORDER BY date DESC LIMIT :limit")
116+
suspend fun getThreadLatestMessages(
117+
chatId: Long,
118+
threadId: Long,
119+
limit: Int
120+
): List<MessageEntity>
50121

51122
@Query("SELECT * FROM messages WHERE chatId = :chatId AND id IN (:messageIds)")
52123
suspend fun getMessagesByIds(chatId: Long, messageIds: List<Long>): List<MessageEntity>

data/src/main/java/org/monogram/data/db/model/MessageEntity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import androidx.room.Index
99
indices = [
1010
Index(value = ["chatId", "date"]),
1111
Index(value = ["chatId", "id"]),
12+
Index(value = ["chatId", "threadId", "date"]),
13+
Index(value = ["chatId", "threadId", "id"]),
1214
Index(value = ["createdAt"])
1315
]
1416
)

data/src/main/java/org/monogram/data/di/dataModule.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,8 @@ val dataModule = module {
277277
MonogramMigrations.MIGRATION_32_33,
278278
MonogramMigrations.MIGRATION_33_34,
279279
MonogramMigrations.MIGRATION_34_35,
280-
MonogramMigrations.MIGRATION_35_36
280+
MonogramMigrations.MIGRATION_35_36,
281+
MonogramMigrations.MIGRATION_36_37
281282
)
282283
.build()
283284
}

data/src/main/java/org/monogram/data/infra/OfflineWarmup.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class OfflineWarmup(
7676
userIds.add(chat.privateUserId)
7777
}
7878
chat.messageSenderId?.takeIf { it > 0 }?.let { userIds.add(it) }
79-
messageDao.getLatestMessages(chat.id, 20)
79+
messageDao.getMainChatLatestMessages(chat.id, 20)
8080
.asSequence()
8181
.map { it.senderId }
8282
.filter { it > 0L }
@@ -205,7 +205,7 @@ class OfflineWarmup(
205205
private suspend fun warmupMessages(chats: List<ChatEntity>) {
206206
val targetChats = chats.take(30)
207207
for (chat in targetChats) {
208-
val alreadyCachedCount = messageDao.getLatestMessages(chat.id, 25).size
208+
val alreadyCachedCount = messageDao.getMainChatLatestMessages(chat.id, 25).size
209209
if (alreadyCachedCount >= 25) {
210210
continue
211211
}

0 commit comments

Comments
 (0)