feat(logger_history): conversation title

This commit is contained in:
rhunk 2024-03-27 19:08:42 +01:00
parent e67dc157e6
commit 73a03b80ae
4 changed files with 95 additions and 67 deletions

View File

@ -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)
}
}

View File

@ -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()

View File

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

View File

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