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