fix(core): feed entry table

Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
rhunk 2025-01-02 17:41:50 +01:00
parent bf5d57758c
commit f854b217a2
7 changed files with 106 additions and 53 deletions

View File

@ -45,6 +45,7 @@ class AddFriendDialog(
id: String, id: String,
bitmoji: String? = null, bitmoji: String? = null,
name: String, name: String,
participantsCount: Int? = null,
getCurrentState: () -> Boolean, getCurrentState: () -> Boolean,
onState: (Boolean) -> Unit = {}, onState: (Boolean) -> Unit = {},
) { ) {
@ -74,13 +75,25 @@ class AddFriendDialog(
size = 32, size = 32,
) )
Column(
modifier = Modifier
.weight(1f)
) {
Text( Text(
text = name, text = name,
fontSize = 15.sp, fontSize = 15.sp,
modifier = Modifier
.weight(1f)
) )
participantsCount?.let {
Text(
text = translation.format("participants_text", "count" to it.toString()),
fontSize = 12.sp,
lineHeight = 12.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
Checkbox( Checkbox(
checked = currentState, checked = currentState,
onCheckedChange = { onCheckedChange = {
@ -249,6 +262,7 @@ class AddFriendDialog(
ListCardEntry( ListCardEntry(
id = group.conversationId, id = group.conversationId,
name = group.name, name = group.name,
participantsCount = group.participantsCount,
getCurrentState = { actionHandler.getGroupState(group) } getCurrentState = { actionHandler.getGroupState(group) }
) { state -> ) { state ->
actionHandler.onGroupState(group, state) actionHandler.onGroupState(group, state)

View File

@ -198,7 +198,8 @@
"search_hint": "Search", "search_hint": "Search",
"fetch_error": "Failed to fetch data", "fetch_error": "Failed to fetch data",
"category_groups": "Groups", "category_groups": "Groups",
"category_friends": "Friends" "category_friends": "Friends",
"participants_text": "{count} participants"
}, },
"scripting_warning": { "scripting_warning": {
"title": "Warning", "title": "Warning",

View File

@ -3,13 +3,14 @@ package me.rhunk.snapenhance.common.database.impl
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.database.Cursor import android.database.Cursor
import me.rhunk.snapenhance.common.database.DatabaseObject import me.rhunk.snapenhance.common.database.DatabaseObject
import me.rhunk.snapenhance.common.util.ktx.getBlobOrNull
import me.rhunk.snapenhance.common.util.ktx.getIntOrNull 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.getLong
import me.rhunk.snapenhance.common.util.ktx.getStringOrNull import me.rhunk.snapenhance.common.util.ktx.getStringOrNull
import java.nio.ByteBuffer
import java.util.UUID
data class FriendFeedEntry( data class FriendFeedEntry(
var id: Int = 0,
var feedDisplayName: String? = null, var feedDisplayName: String? = null,
var participantsSize: Int = 0, var participantsSize: Int = 0,
var lastInteractionTimestamp: Long = 0, var lastInteractionTimestamp: Long = 0,
@ -18,24 +19,28 @@ data class FriendFeedEntry(
var lastInteractionUserId: Int? = null, var lastInteractionUserId: Int? = null,
var key: String? = null, var key: String? = null,
var friendUserId: String? = null, var friendUserId: String? = null,
var participants: List<String>? = null,
var conversationType: Int? = null,
var friendDisplayName: String? = null, var friendDisplayName: String? = null,
var friendDisplayUsername: String? = null, var friendDisplayUsername: String? = null,
var friendLinkType: Int? = null, var friendLinkType: Int? = null,
var bitmojiAvatarId: String? = null, var bitmojiAvatarId: String? = null,
var bitmojiSelfieId: String? = null, var bitmojiSelfieId: String? = null,
) : DatabaseObject { ) : DatabaseObject {
@SuppressLint("Range") @SuppressLint("Range")
override fun write(cursor: Cursor) { override fun write(cursor: Cursor) {
with(cursor) { with(cursor) {
id = getInteger("_id") key = getStringOrNull("client_conversation_id") ?: getStringOrNull("key")
feedDisplayName = getStringOrNull("feedDisplayName") feedDisplayName = (getStringOrNull("conversation_title") ?: getStringOrNull("feedDisplayName"))?.takeIf { it.isNotBlank() }
participantsSize = getInteger("participantsSize") lastInteractionTimestamp = getLongOrNull("last_updated_timestamp") ?: getLongOrNull("lastInteractionTimestamp") ?: 0L
lastInteractionTimestamp = getLong("lastInteractionTimestamp")
displayTimestamp = getLong("displayTimestamp") participants = getBlobOrNull("participants")?.toList()?.chunked(16)?.map { ByteBuffer.wrap(it.toByteArray()).run { UUID(long, long) }.toString() } ?: emptyList()
participantsSize = getIntOrNull("participantsSize") ?: participants?.size ?: 0
conversationType = getIntOrNull("conversation_type") ?: getIntOrNull("kind")
displayTimestamp = getLongOrNull("displayTimestamp") ?: 0L
displayInteractionType = getStringOrNull("displayInteractionType") displayInteractionType = getStringOrNull("displayInteractionType")
lastInteractionUserId = getIntOrNull("lastInteractionUserId") lastInteractionUserId = getIntOrNull("lastInteractionUserId")
key = getStringOrNull("key")
friendUserId = getStringOrNull("friendUserId") friendUserId = getStringOrNull("friendUserId")
friendDisplayName = getStringOrNull("friendDisplayName") friendDisplayName = getStringOrNull("friendDisplayName")
friendDisplayUsername = getStringOrNull("friendDisplayUsername") friendDisplayUsername = getStringOrNull("friendDisplayUsername")

View File

@ -330,22 +330,25 @@ class SnapEnhance {
event.canceled = true event.canceled = true
val feedEntries = appContext.database.getFeedEntries(Int.MAX_VALUE) val feedEntries = appContext.database.getFeedEntries(Int.MAX_VALUE)
val groups = feedEntries.filter { it.friendUserId == null }.map { val groups = feedEntries.filter { it.conversationType == 1 }.map {
MessagingGroupInfo( MessagingGroupInfo(
it.key!!, it.key!!,
it.feedDisplayName!!, it.feedDisplayName ?: "",
it.participantsSize it.participantsSize
) )
} }
val friends = feedEntries.filter { it.friendUserId != null }.map { val friends = feedEntries.filter { it.conversationType == 0 }.mapNotNull {
val friendUserId = it.friendUserId ?: it.participants?.filter { it != appContext.database.myUserId }?.firstOrNull() ?: return@mapNotNull null
val friend = appContext.database.getFriendInfo(friendUserId) ?: return@mapNotNull null
MessagingFriendInfo( MessagingFriendInfo(
it.friendUserId!!, friendUserId,
appContext.database.getConversationLinkFromUserId(it.friendUserId!!)?.clientConversationId, appContext.database.getConversationLinkFromUserId(friendUserId)?.clientConversationId,
it.friendDisplayName, friend.displayName,
it.friendDisplayUsername!!.split("|")[1], friend.mutableUsername ?: friend.usernameForSorting!!,
it.bitmojiAvatarId, friend.bitmojiAvatarId,
it.bitmojiSelfieId, friend.bitmojiSelfieId,
streaks = null streaks = null
) )
} }
@ -379,7 +382,7 @@ class SnapEnhance {
return appContext.database.getFeedEntryByConversationId(uuid)?.let { return appContext.database.getFeedEntryByConversationId(uuid)?.let {
MessagingGroupInfo( MessagingGroupInfo(
it.key!!, it.key!!,
it.feedDisplayName!!, it.feedDisplayName ?: "",
it.participantsSize it.participantsSize
).toSerialized() ).toSerialized()
} }

View File

@ -22,10 +22,12 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.* import kotlinx.coroutines.*
import me.rhunk.snapenhance.common.data.ContentType import me.rhunk.snapenhance.common.data.ContentType
import me.rhunk.snapenhance.common.database.impl.FriendFeedEntry import me.rhunk.snapenhance.common.database.impl.FriendFeedEntry
import me.rhunk.snapenhance.common.ui.createComposeAlertDialog import me.rhunk.snapenhance.common.ui.createComposeAlertDialog
import me.rhunk.snapenhance.common.ui.rememberAsyncMutableState
import me.rhunk.snapenhance.core.action.AbstractAction import me.rhunk.snapenhance.core.action.AbstractAction
import me.rhunk.snapenhance.core.features.impl.messaging.Messaging import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
import me.rhunk.snapenhance.core.logger.CoreLogger import me.rhunk.snapenhance.core.logger.CoreLogger
@ -72,6 +74,8 @@ class ExportChatMessages : AbstractAction() {
val messageTypeFilter = remember { mutableStateListOf<ContentType>() } val messageTypeFilter = remember { mutableStateListOf<ContentType>() }
var amountOfMessages by remember { mutableIntStateOf(-1) } var amountOfMessages by remember { mutableIntStateOf(-1) }
var downloadMedias by remember { mutableStateOf(false) } var downloadMedias by remember { mutableStateOf(false) }
val allFriends by rememberAsyncMutableState(null) { context.database.getAllFriends().associateBy { it.userId!! } }
val myUserId = context.database.myUserId
Column( Column(
modifier = Modifier modifier = Modifier
@ -102,7 +106,7 @@ class ExportChatMessages : AbstractAction() {
LazyColumn( LazyColumn(
modifier = Modifier.size(LocalConfiguration.current.screenWidthDp.dp, 300.dp) modifier = Modifier.size(LocalConfiguration.current.screenWidthDp.dp, 300.dp)
) { ) {
items(feedEntries) { feedEntry -> items(feedEntries, key = { it.key!! }) { feedEntry ->
DropdownMenuItem( DropdownMenuItem(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
onClick = { onClick = {
@ -114,11 +118,26 @@ class ExportChatMessages : AbstractAction() {
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Checkbox(checked = selectedFeedEntries.contains(feedEntry), onCheckedChange = null) Checkbox(checked = selectedFeedEntries.contains(feedEntry), onCheckedChange = null)
Column {
Text( Text(
text = feedEntry.feedDisplayName ?: feedEntry.friendDisplayName ?: "unknown", text = remember(feedEntry) {
(if (feedEntry.conversationType == 1) feedEntry.feedDisplayName else feedEntry.participants?.filter { it != myUserId }?.firstOrNull()?.let { userId ->
allFriends?.get(userId)?.let { friend -> friend.displayName?.let { "$it (${friend.mutableUsername})" } ?: friend.mutableUsername }
}) ?: "Unknown"
},
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
lineHeight = 15.sp,
maxLines = 1 maxLines = 1
) )
if (feedEntry.conversationType == 1) {
Text(
text = "${feedEntry.participantsSize} participants",
fontSize = 10.sp,
lineHeight = 15.sp,
overflow = TextOverflow.Ellipsis,
)
}
}
} }
} }
) )
@ -332,12 +351,13 @@ class ExportChatMessages : AbstractAction() {
) { ) {
//first fetch the first message //first fetch the first message
val conversationId = feedEntry.key!! val conversationId = feedEntry.key!!
val conversationName = feedEntry.feedDisplayName ?: feedEntry.friendDisplayName!!.split("|").lastOrNull() ?: "unknown"
val conversationParticipants = context.database.getConversationParticipants(feedEntry.key!!, useCache = false) val conversationParticipants = context.database.getConversationParticipants(feedEntry.key!!, useCache = false)
?.mapNotNull { ?.mapNotNull {
context.database.getFriendInfo(it) context.database.getFriendInfo(it)
}?.associateBy { it.userId!! } ?: emptyMap() }?.associateBy { it.userId!! } ?: emptyMap()
val conversationName = feedEntry.feedDisplayName ?: conversationParticipants.values.take(3).joinToString("_") { it.mutableUsername ?: "" }
val publicFolder = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "SnapEnhance").also { if (!it.exists()) it.mkdirs() } val publicFolder = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "SnapEnhance").also { if (!it.exists()) it.mkdirs() }
val outputFile = publicFolder.resolve("conversation_${conversationName}_${System.currentTimeMillis()}.${exportParams.exportFormat.extension}") val outputFile = publicFolder.resolve("conversation_${conversationName}_${System.currentTimeMillis()}.${exportParams.exportFormat.extension}")

View File

@ -152,17 +152,6 @@ class DatabaseAccess(
obj obj
} }
fun getFeedEntryByUserId(userId: String): FriendFeedEntry? {
return useDatabase(DatabaseType.MAIN)?.performOperation {
readDatabaseObject(
FriendFeedEntry(),
"FriendsFeedView",
"friendUserId = ?",
arrayOf(userId)
)
}
}
val myUserId by lazy { val myUserId by lazy {
context.androidContext.getSharedPreferences("user_session_shared_pref", 0).getString("key_user_id", null) ?: context.androidContext.getSharedPreferences("user_session_shared_pref", 0).getString("key_user_id", null) ?:
useDatabase(DatabaseType.ARROYO)?.performOperation { useDatabase(DatabaseType.ARROYO)?.performOperation {
@ -178,7 +167,14 @@ class DatabaseAccess(
} }
fun getFeedEntryByConversationId(conversationId: String): FriendFeedEntry? { fun getFeedEntryByConversationId(conversationId: String): FriendFeedEntry? {
return useDatabase(DatabaseType.MAIN)?.performOperation { return useDatabase(DatabaseType.ARROYO)?.performOperation {
readDatabaseObject(
FriendFeedEntry(),
"feed_entry",
"client_conversation_id = ?",
arrayOf(conversationId)
)
} ?: useDatabase(DatabaseType.MAIN)?.performOperation {
readDatabaseObject( readDatabaseObject(
FriendFeedEntry(), FriendFeedEntry(),
"FriendsFeedView", "FriendsFeedView",
@ -230,20 +226,34 @@ class DatabaseAccess(
} }
fun getFeedEntries(limit: Int): List<FriendFeedEntry> { fun getFeedEntries(limit: Int): List<FriendFeedEntry> {
return useDatabase(DatabaseType.MAIN)?.performOperation { val entries = mutableListOf<FriendFeedEntry>()
return useDatabase(DatabaseType.ARROYO)?.performOperation {
safeRawQuery( safeRawQuery(
"SELECT * FROM FriendsFeedView ORDER BY _id LIMIT ?", "SELECT * FROM feed_entry ORDER BY last_updated_timestamp DESC LIMIT ?",
arrayOf(limit.toString()) arrayOf(limit.toString())
)?.use { query -> )?.use { query ->
val list = mutableListOf<FriendFeedEntry>()
while (query.moveToNext()) { while (query.moveToNext()) {
val friendFeedEntry = FriendFeedEntry() val friendFeedEntry = FriendFeedEntry()
try { try {
friendFeedEntry.write(query) friendFeedEntry.write(query)
} catch (_: Throwable) {} } catch (_: Throwable) {}
list.add(friendFeedEntry) entries.add(friendFeedEntry)
} }
list entries
}
}?.takeIf { it.isNotEmpty() } ?: useDatabase(DatabaseType.MAIN)?.performOperation {
safeRawQuery(
"SELECT * FROM FriendsFeedView ORDER BY _id LIMIT ?",
arrayOf(limit.toString())
)?.use { query ->
while (query.moveToNext()) {
val friendFeedEntry = FriendFeedEntry()
try {
friendFeedEntry.write(query)
} catch (_: Throwable) {}
entries.add(friendFeedEntry)
}
entries
} }
} ?: emptyList() } ?: emptyList()
} }

View File

@ -91,15 +91,15 @@ class HalfSwipeNotifier : Feature("Half Swipe Notifier") {
if (minDuration > peekingDuration || maxDuration < peekingDuration) return if (minDuration > peekingDuration || maxDuration < peekingDuration) return
val groupName = context.database.getFeedEntryByConversationId(conversationId)?.feedDisplayName val feedEntry = context.database.getFeedEntryByConversationId(conversationId)
val friendInfo = context.database.getFriendInfo(userId) ?: return val friendInfo = context.database.getFriendInfo(userId) ?: return
Notification.Builder(context.androidContext, channelId) Notification.Builder(context.androidContext, channelId)
.setContentTitle(groupName ?: friendInfo.displayName ?: friendInfo.mutableUsername) .setContentTitle(feedEntry?.feedDisplayName ?: friendInfo.displayName ?: friendInfo.mutableUsername)
.setContentText(if (groupName != null) { .setContentText(if (feedEntry?.conversationType == 1) {
translation.format("notification_content_group", translation.format("notification_content_group",
"friend" to (friendInfo.displayName ?: friendInfo.mutableUsername).toString(), "friend" to (friendInfo.displayName ?: friendInfo.mutableUsername).toString(),
"group" to groupName, "group" to (feedEntry.feedDisplayName ?: "Group"),
"duration" to peekingDuration.toString() "duration" to peekingDuration.toString()
) )
} else { } else {