mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-29 13:00:17 +02:00
feat(logger_history): conversation title
This commit is contained in:
parent
e67dc157e6
commit
73a03b80ae
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
Loading…
x
Reference in New Issue
Block a user