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:
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,