commit 73a03b80aece4eaba1a08fad008a37e1c25ba47f
parent e67dc157e61af20f9152b2ac5b497216f854891d
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Wed, 27 Mar 2024 19:08:42 +0100

feat(logger_history): conversation title

Diffstat:
Mapp/src/main/kotlin/me/rhunk/snapenhance/messaging/ModDatabase.kt | 61++++++++++++++++---------------------------------------------
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/LoggerHistoryRoot.kt | 59+++++++++++++++++++++++++++++++++++++++--------------------
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/data/MessagingCoreObjects.kt | 40++++++++++++++++++++++++++++++++++++++--
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt | 2++
4 files changed, 95 insertions(+), 67 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/messaging/ModDatabase.kt b/app/src/main/kotlin/me/rhunk/snapenhance/messaging/ModDatabase.kt @@ -37,7 +37,8 @@ class ModDatabase( SQLiteDatabaseHelper.createTablesFromSchema(database, mapOf( "friends" to listOf( "id INTEGER PRIMARY KEY AUTOINCREMENT", - "userId VARCHAR UNIQUE", + "userId CHAR(36) UNIQUE", + "dmConversationId VARCHAR(36)", "displayName VARCHAR", "mutableUsername VARCHAR", "bitmojiId VARCHAR", @@ -45,7 +46,7 @@ class ModDatabase( ), "groups" to listOf( "id INTEGER PRIMARY KEY AUTOINCREMENT", - "conversationId VARCHAR UNIQUE", + "conversationId CHAR(36) UNIQUE", "name VARCHAR", "participantsCount INTEGER" ), @@ -87,13 +88,7 @@ class ModDatabase( return database.rawQuery("SELECT * FROM groups", null).use { cursor -> val groups = mutableListOf<MessagingGroupInfo>() while (cursor.moveToNext()) { - groups.add( - MessagingGroupInfo( - conversationId = cursor.getStringOrNull("conversationId")!!, - name = cursor.getStringOrNull("name")!!, - participantsCount = cursor.getInteger("participantsCount") - ) - ) + groups.add(MessagingGroupInfo.fromCursor(cursor)) } groups } @@ -104,22 +99,7 @@ class ModDatabase( val friends = mutableListOf<MessagingFriendInfo>() while (cursor.moveToNext()) { runCatching { - friends.add( - MessagingFriendInfo( - userId = cursor.getStringOrNull("userId")!!, - displayName = cursor.getStringOrNull("displayName"), - mutableUsername = cursor.getStringOrNull("mutableUsername")!!, - bitmojiId = cursor.getStringOrNull("bitmojiId"), - selfieId = cursor.getStringOrNull("selfieId"), - streaks = cursor.getLongOrNull("expirationTimestamp")?.let { - FriendStreaks( - notify = cursor.getInteger("notify") == 1, - expirationTimestamp = it, - length = cursor.getInteger("length") - ) - } - ) - ) + friends.add(MessagingFriendInfo.fromCursor(cursor)) }.onFailure { context.log.error("Failed to parse friend", it) } @@ -147,9 +127,10 @@ class ModDatabase( executeAsync { try { database.execSQL( - "INSERT OR REPLACE INTO friends (userId, displayName, mutableUsername, bitmojiId, selfieId) VALUES (?, ?, ?, ?, ?)", + "INSERT OR REPLACE INTO friends (userId, dmConversationId, displayName, mutableUsername, bitmojiId, selfieId) VALUES (?, ?, ?, ?, ?, ?)", arrayOf( friend.userId, + friend.dmConversationId, friend.displayName, friend.mutableUsername, friend.bitmojiId, @@ -208,20 +189,14 @@ class ModDatabase( fun getFriendInfo(userId: String): MessagingFriendInfo? { return database.rawQuery("SELECT * FROM friends LEFT OUTER JOIN streaks ON friends.userId = streaks.id WHERE userId = ?", arrayOf(userId)).use { cursor -> if (!cursor.moveToFirst()) return@use null - MessagingFriendInfo( - userId = cursor.getStringOrNull("userId")!!, - displayName = cursor.getStringOrNull("displayName"), - mutableUsername = cursor.getStringOrNull("mutableUsername")!!, - bitmojiId = cursor.getStringOrNull("bitmojiId"), - selfieId = cursor.getStringOrNull("selfieId"), - streaks = cursor.getLongOrNull("expirationTimestamp")?.let { - FriendStreaks( - notify = cursor.getInteger("notify") == 1, - expirationTimestamp = it, - length = cursor.getInteger("length") - ) - } - ) + MessagingFriendInfo.fromCursor(cursor) + } + } + + fun findFriend(conversationId: String): MessagingFriendInfo? { + return database.rawQuery("SELECT * FROM friends WHERE dmConversationId = ?", arrayOf(conversationId)).use { cursor -> + if (!cursor.moveToFirst()) return@use null + MessagingFriendInfo.fromCursor(cursor) } } @@ -243,11 +218,7 @@ class ModDatabase( fun getGroupInfo(conversationId: String): MessagingGroupInfo? { return database.rawQuery("SELECT * FROM groups WHERE conversationId = ?", arrayOf(conversationId)).use { cursor -> if (!cursor.moveToFirst()) return@use null - MessagingGroupInfo( - conversationId = cursor.getStringOrNull("conversationId")!!, - name = cursor.getStringOrNull("name")!!, - participantsCount = cursor.getInteger("participantsCount") - ) + MessagingGroupInfo.fromCursor(cursor) } } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/LoggerHistoryRoot.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/LoggerHistoryRoot.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.navigation.NavBackStackEntry @@ -36,6 +37,8 @@ import me.rhunk.snapenhance.core.features.impl.downloader.decoder.DecodedAttachm import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder import me.rhunk.snapenhance.download.DownloadProcessor import me.rhunk.snapenhance.ui.manager.Routes +import java.nio.ByteBuffer +import java.util.UUID import kotlin.math.absoluteValue @@ -45,12 +48,15 @@ class LoggerHistoryRoot : Routes.Route() { private var stringFilter by mutableStateOf("") private var reverseOrder by mutableStateOf(true) - private inline fun decodeMessage(message: LoggedMessage, result: (contentType: ContentType, messageReader: ProtoReader, attachments: List<DecodedAttachment>) -> Unit) { + private inline fun decodeMessage(message: LoggedMessage, result: (senderId: String?, contentType: ContentType, messageReader: ProtoReader, attachments: List<DecodedAttachment>) -> Unit) { runCatching { val messageObject = JsonParser.parseString(String(message.messageData, Charsets.UTF_8)).asJsonObject + val senderId = messageObject.getAsJsonObject("mSenderId")?.getAsJsonArray("mId")?.map { it.asByte }?.toByteArray()?.let { + ByteBuffer.wrap(it).run { UUID(long, long) }.toString() + } val messageContent = messageObject.getAsJsonObject("mMessageContent") val messageReader = messageContent.getAsJsonArray("mContent").map { it.asByte }.toByteArray().let { ProtoReader(it) } - result(ContentType.fromMessageContainer(messageReader) ?: ContentType.UNKNOWN, messageReader, MessageDecoder.decode(messageContent)) + result(senderId, ContentType.fromMessageContainer(messageReader) ?: ContentType.UNKNOWN, messageReader, MessageDecoder.decode(messageContent)) }.onFailure { context.log.error("Failed to decode message", it) } @@ -119,23 +125,32 @@ class LoggerHistoryRoot : Routes.Route() { LaunchedEffect(Unit, message) { runCatching { - decodeMessage(message) { contentType, messageReader, attachments -> + decodeMessage(message) { senderId, contentType, messageReader, attachments -> + val senderUsername = senderId?.let { context.modDatabase.getFriendInfo(it)?.mutableUsername } ?: "unknown sender" + + @Composable + fun ContentHeader() { + Text("$senderUsername (${contentType.toString().lowercase()})", modifier = Modifier.padding(end = 4.dp), fontWeight = FontWeight.ExtraLight) + } + if (contentType == ContentType.CHAT) { val content = messageReader.getString(2, 1) ?: "[empty chat message]" contentView = { - Text(content, modifier = Modifier - .fillMaxWidth() - .pointerInput(Unit) { - detectTapGestures(onLongPress = { - context.androidContext.copyToClipboard(content) + Column { + Text(content, modifier = Modifier + .fillMaxWidth() + .pointerInput(Unit) { + detectTapGestures(onLongPress = { + context.androidContext.copyToClipboard(content) + }) }) - }) + ContentHeader() + } } return@runCatching } contentView = { Column column@{ - Text("[$contentType]") if (attachments.isEmpty()) return@column FlowRow( @@ -164,6 +179,7 @@ class LoggerHistoryRoot : Routes.Route() { } } } + ContentHeader() } } } @@ -194,8 +210,15 @@ class LoggerHistoryRoot : Routes.Route() { expanded = expanded, onExpandedChange = { expanded = it }, ) { + fun formatConversationId(conversationId: String?): String? { + if (conversationId == null) return null + return context.modDatabase.getGroupInfo(conversationId)?.name?.let { "Group $it" } ?: context.modDatabase.findFriend(conversationId)?.let { + "Friend " + (it.displayName?.let { name -> "$name (${it.mutableUsername})" } ?: it.mutableUsername) + } ?: conversationId + } + OutlinedTextField( - value = selectedConversation ?: "Select a conversation", + value = remember(selectedConversation) { formatConversationId(selectedConversation) ?: "Select a conversation" }, onValueChange = {}, readOnly = true, modifier = Modifier @@ -213,12 +236,12 @@ class LoggerHistoryRoot : Routes.Route() { } ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) { - conversations.forEach { conversation -> + conversations.forEach { conversationId -> DropdownMenuItem(onClick = { - selectedConversation = conversation + selectedConversation = conversationId expanded = false }, text = { - Text(conversation) + Text(remember(conversationId) { formatConversationId(conversationId) ?: "Unknown conversation" }) }) } } @@ -278,7 +301,7 @@ class LoggerHistoryRoot : Routes.Route() { ) { messageData -> if (stringFilter.isEmpty()) return@fetchMessages true var isMatch = false - decodeMessage(messageData) { contentType, messageReader, _ -> + decodeMessage(messageData) { _, contentType, messageReader, _ -> if (contentType == ContentType.CHAT) { val content = messageReader.getString(2, 1) ?: return@decodeMessage isMatch = content.contains(stringFilter, ignoreCase = true) @@ -312,6 +335,7 @@ class LoggerHistoryRoot : Routes.Route() { value = searchValue, onValueChange = { keyword -> searchValue = keyword + stringFilter = keyword }, keyboardActions = KeyboardActions(onDone = { focusRequester.freeFocus() }), modifier = Modifier @@ -329,11 +353,6 @@ class LoggerHistoryRoot : Routes.Route() { cursorColor = MaterialTheme.colorScheme.primary ) ) - ElevatedButton(onClick = { - stringFilter = searchValue - }) { - Text("Search") - } LaunchedEffect(Unit) { focusRequester.requestFocus() diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/data/MessagingCoreObjects.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/data/MessagingCoreObjects.kt @@ -1,8 +1,13 @@ package me.rhunk.snapenhance.common.data +import android.database.Cursor import android.os.Parcelable import kotlinx.parcelize.Parcelize import me.rhunk.snapenhance.common.config.FeatureNotice +import me.rhunk.snapenhance.common.util.ktx.getIntOrNull +import me.rhunk.snapenhance.common.util.ktx.getInteger +import me.rhunk.snapenhance.common.util.ktx.getLongOrNull +import me.rhunk.snapenhance.common.util.ktx.getStringOrNull import kotlin.time.Duration.Companion.hours @@ -71,17 +76,48 @@ data class MessagingGroupInfo( val conversationId: String, val name: String, val participantsCount: Int -): Parcelable +): Parcelable { + companion object { + fun fromCursor(cursor: Cursor): MessagingGroupInfo { + return MessagingGroupInfo( + conversationId = cursor.getStringOrNull("conversationId")!!, + name = cursor.getStringOrNull("name")!!, + participantsCount = cursor.getInteger("participantsCount") + ) + } + } +} @Parcelize data class MessagingFriendInfo( val userId: String, + val dmConversationId: String?, val displayName: String?, val mutableUsername: String, val bitmojiId: String?, val selfieId: String?, var streaks: FriendStreaks?, -): Parcelable +): Parcelable { + companion object { + fun fromCursor(cursor: Cursor): MessagingFriendInfo { + return MessagingFriendInfo( + userId = cursor.getStringOrNull("userId")!!, + dmConversationId = cursor.getStringOrNull("dmConversationId"), + displayName = cursor.getStringOrNull("displayName"), + mutableUsername = cursor.getStringOrNull("mutableUsername")!!, + bitmojiId = cursor.getStringOrNull("bitmojiId"), + selfieId = cursor.getStringOrNull("selfieId"), + streaks = cursor.getLongOrNull("expirationTimestamp")?.let { + FriendStreaks( + notify = cursor.getIntOrNull("notify") == 1, + expirationTimestamp = it, + length = cursor.getIntOrNull("length") ?: 0 + ) + } + ) + } + } +} class StoryData( val url: String, diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt @@ -261,6 +261,7 @@ class SnapEnhance { val friends = feedEntries.filter { it.friendUserId != null }.map { MessagingFriendInfo( it.friendUserId!!, + appContext.database.getConversationLinkFromUserId(it.friendUserId!!)?.clientConversationId, it.friendDisplayName, it.friendDisplayUsername!!.split("|")[1], it.bitmojiAvatarId, @@ -279,6 +280,7 @@ class SnapEnhance { return appContext.database.getFriendInfo(uuid)?.let { MessagingFriendInfo( userId = it.userId!!, + dmConversationId = appContext.database.getConversationLinkFromUserId(it.userId!!)?.clientConversationId, displayName = it.displayName, mutableUsername = it.mutableUsername!!, bitmojiId = it.bitmojiAvatarId,