commit bcbf54b35aa4c98ae1f58ef0a951d58c5741003a
parent 31ae06ed625d87fa6ab6ced8b774938ddb6c1f24
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Sun, 9 Jun 2024 19:50:28 +0200
feat(ui/chat_action_menu): show chat edit history
Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
Diffstat:
8 files changed, 120 insertions(+), 32 deletions(-)
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
@@ -143,7 +143,7 @@ class LoggerHistoryRoot : Routes.Route() {
})
val edits by rememberAsyncMutableState(defaultValue = emptyList()) {
- loggerWrapper.getMessageEdits(selectedConversation!!, message.messageId)
+ loggerWrapper.getChatEdits(selectedConversation!!, message.messageId)
}
edits.forEach { messageEdit ->
val date = remember {
@@ -152,10 +152,10 @@ class LoggerHistoryRoot : Routes.Route() {
Text(
modifier = Modifier.pointerInput(Unit) {
detectTapGestures(onLongPress = {
- context.androidContext.copyToClipboard(messageEdit.messageText)
+ context.androidContext.copyToClipboard(messageEdit.message)
})
}.fillMaxWidth().padding(start = 4.dp),
- text = messageEdit.messageText + " (edited at $date)",
+ text = messageEdit.message + " (edited at $date)",
fontWeight = FontWeight.Light,
fontStyle = FontStyle.Italic,
fontSize = 12.sp
diff --git a/common/src/main/aidl/me/rhunk/snapenhance/bridge/logger/LoggedChatEdit.aidl b/common/src/main/aidl/me/rhunk/snapenhance/bridge/logger/LoggedChatEdit.aidl
@@ -0,0 +1,6 @@
+package me.rhunk.snapenhance.bridge.logger;
+
+parcelable LoggedChatEdit {
+ long timestamp;
+ String message;
+}+
\ No newline at end of file
diff --git a/common/src/main/aidl/me/rhunk/snapenhance/bridge/logger/LoggerInterface.aidl b/common/src/main/aidl/me/rhunk/snapenhance/bridge/logger/LoggerInterface.aidl
@@ -1,6 +1,7 @@
package me.rhunk.snapenhance.bridge.logger;
import me.rhunk.snapenhance.bridge.logger.BridgeLoggedMessage;
+import me.rhunk.snapenhance.bridge.logger.LoggedChatEdit;
interface LoggerInterface {
/**
@@ -38,4 +39,6 @@ interface LoggerInterface {
String eventType,
String data
);
+
+ List<LoggedChatEdit> getChatEdits(String conversationId, long messageId);
}
\ No newline at end of file
diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json
@@ -1407,6 +1407,7 @@
"preview_button": "Preview",
"download_button": "Download",
"delete_logged_message_button": "Delete Logged Message",
+ "show_chat_edit_history": "Show Chat Edit History",
"convert_message": "Convert Message",
"edit_message": "Edit Message"
},
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/LoggerWrapper.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/LoggerWrapper.kt
@@ -7,6 +7,7 @@ import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import kotlinx.coroutines.*
import me.rhunk.snapenhance.bridge.logger.BridgeLoggedMessage
+import me.rhunk.snapenhance.bridge.logger.LoggedChatEdit
import me.rhunk.snapenhance.bridge.logger.LoggerInterface
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
import me.rhunk.snapenhance.common.data.StoryData
@@ -20,11 +21,6 @@ import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
import java.io.File
import java.util.UUID
-class LoggedMessageEdit(
- val timestamp: Long,
- val messageText: String
-)
-
class LoggedMessage(
val messageId: Long,
val conversationId: String,
@@ -422,17 +418,17 @@ class LoggerWrapper(
return ConversationInfo(conversationId, participantSize, groupTitle, usernames)
}
- fun getMessageEdits(conversationId: String, messageId: Long): List<LoggedMessageEdit> {
- val edits = mutableListOf<LoggedMessageEdit>()
+ override fun getChatEdits(conversationId: String, messageId: Long): List<LoggedChatEdit> {
+ val edits = mutableListOf<LoggedChatEdit>()
database.rawQuery(
- "SELECT added_timestamp, message_text FROM chat_edits WHERE conversation_id = ? AND message_id = ?",
+ "SELECT added_timestamp, message_text FROM chat_edits WHERE conversation_id = ? AND message_id = ? ORDER BY added_timestamp ASC",
arrayOf(conversationId, messageId.toString())
- ).use {
- while (it.moveToNext()) {
- edits.add(LoggedMessageEdit(
- timestamp = it.getLongOrNull("added_timestamp") ?: continue,
- messageText = it.getStringOrNull("message_text") ?: continue
- ))
+ ).use { cursor ->
+ while (cursor.moveToNext()) {
+ edits.add(LoggedChatEdit().apply {
+ timestamp = cursor.getLongOrNull("added_timestamp") ?: return@apply
+ message = cursor.getStringOrNull("message_text")
+ }.takeIf { it.timestamp > 0L } ?: continue)
}
}
return edits
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/spying/MessageLogger.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/spying/MessageLogger.kt
@@ -8,6 +8,7 @@ import android.os.DeadObjectException
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import me.rhunk.snapenhance.bridge.logger.BridgeLoggedMessage
+import me.rhunk.snapenhance.bridge.logger.LoggedChatEdit
import me.rhunk.snapenhance.common.data.ContentType
import me.rhunk.snapenhance.common.data.MessageState
import me.rhunk.snapenhance.common.data.QuotedMessageContentStatus
@@ -76,6 +77,11 @@ class MessageLogger : Feature("MessageLogger",
}
}
+ fun getChatEdits(conversationId: String, clientMessageId: Long): List<LoggedChatEdit> {
+ val uniqueMessageId = makeUniqueIdentifier(conversationId, clientMessageId) ?: return emptyList()
+ return loggerInterface.getChatEdits(conversationId, uniqueMessageId)
+ }
+
private fun computeMessageIdentifier(conversationId: String, orderKey: Long) = (orderKey.toString() + conversationId).longHashCode()
private fun makeUniqueIdentifier(conversationId: String, clientMessageId: Long): Long? {
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt
@@ -5,6 +5,7 @@ import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.Button
import android.widget.LinearLayout
+import me.rhunk.snapenhance.bridge.logger.LoggedChatEdit
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
import me.rhunk.snapenhance.core.features.impl.experiments.ConvertMessageLocally
import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
@@ -131,6 +132,30 @@ class ChatActionMenu : AbstractMenu() {
}
}
})
+
+ injectButton(Button(viewGroup.context).apply {
+ var chatEdits = emptyList<LoggedChatEdit>()
+ text = this@ChatActionMenu.context.translation["chat_action_menu.show_chat_edit_history"]
+ setOnClickListener {
+ menuViewInjector.menu(NewChatActionMenu::class)?.showChatEditHistory(chatEdits)
+ }
+ addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ visibility = View.GONE
+ chatEdits = this@ChatActionMenu.context.feature(MessageLogger::class).getChatEdits(
+ messaging.openedConversationUUID.toString(),
+ messaging.lastFocusedMessageId,
+ )
+ if (chatEdits.isEmpty()) return
+ visibility = View.VISIBLE
+ }
+
+ override fun onViewDetachedFromWindow(v: View) {
+ visibility = View.GONE
+ chatEdits = emptyList()
+ }
+ })
+ })
}
if (context.config.experimental.editMessage.get() && messaging.conversationManager?.isEditMessageSupported() == true) {
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/NewChatActionMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/NewChatActionMenu.kt
@@ -10,29 +10,32 @@ import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Download
-import androidx.compose.material.icons.filled.RemoveRedEye
-import androidx.compose.material.icons.outlined.Image
-import androidx.compose.material.icons.rounded.BookmarkRemove
-import androidx.compose.material.icons.rounded.Edit
-import androidx.compose.material3.Button
-import androidx.compose.material3.Card
-import androidx.compose.material3.Icon
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import androidx.compose.material.icons.outlined.*
+import androidx.compose.material3.*
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import me.rhunk.snapenhance.bridge.logger.LoggedChatEdit
import me.rhunk.snapenhance.common.data.ContentType
+import me.rhunk.snapenhance.common.ui.createComposeAlertDialog
import me.rhunk.snapenhance.common.ui.createComposeView
+import me.rhunk.snapenhance.common.ui.rememberAsyncMutableState
import me.rhunk.snapenhance.common.util.ktx.copyToClipboard
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
import me.rhunk.snapenhance.common.util.protobuf.ProtoWriter
@@ -48,9 +51,11 @@ import me.rhunk.snapenhance.core.ui.debugEditText
import me.rhunk.snapenhance.core.ui.iterateParent
import me.rhunk.snapenhance.core.ui.menu.AbstractMenu
import me.rhunk.snapenhance.core.ui.triggerCloseTouchEvent
+import me.rhunk.snapenhance.core.util.ktx.getIdentifier
import me.rhunk.snapenhance.core.util.ktx.isDarkTheme
import me.rhunk.snapenhance.core.util.ktx.setObjectField
import me.rhunk.snapenhance.core.util.ktx.vibrateLongPress
+import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Date
import kotlin.io.encoding.Base64
@@ -70,6 +75,32 @@ class NewChatActionMenu : AbstractMenu() {
}
}
+ fun showChatEditHistory(
+ edits: List<LoggedChatEdit>,
+ ) {
+ createComposeAlertDialog(context.mainActivity!!) {
+ LazyColumn(
+ modifier = Modifier.padding(16.dp),
+ ) {
+ itemsIndexed(edits) { index, edit ->
+ Column(
+ modifier = Modifier.padding(8.dp).fillMaxWidth().pointerInput(Unit) {
+ detectTapGestures(
+ onLongPress = {
+ context.androidContext.copyToClipboard(edit.message)
+ }
+ )
+ },
+ horizontalAlignment = Alignment.Start,
+ ) {
+ Text(edit.message)
+ Text(text = DateFormat.getDateTimeInstance().format(edit.timestamp) + " (${index + 1})", fontSize = 12.sp, fontWeight = FontWeight.Light)
+ }
+ }
+ }
+ }.show()
+ }
+
fun editCurrentMessage(
localContext: Context,
dismissActionMenu: () -> Unit,
@@ -267,6 +298,11 @@ class NewChatActionMenu : AbstractMenu() {
val composeView = createComposeView(event.view.context) {
val primaryColor = remember { if (event.view.context.isDarkTheme()) Color.White else Color.Black }
+ val avenirNextMediumFont = remember {
+ FontFamily(
+ Font(context.resources.getIdentifier("avenir_next_medium", "font"), FontWeight.Medium)
+ )
+ }
@Composable
fun ListButton(
@@ -288,7 +324,7 @@ class NewChatActionMenu : AbstractMenu() {
tint = primaryColor,
contentDescription = text
)
- Text(text, color = primaryColor)
+ Text(text, color = primaryColor, fontFamily = avenirNextMediumFont, fontSize = 16.sp)
}
Spacer(modifier = Modifier
.height(1.dp)
@@ -300,11 +336,11 @@ class NewChatActionMenu : AbstractMenu() {
modifier = Modifier.fillMaxWidth(),
) {
if (context.config.downloader.downloadContextMenu.get()) {
- ListButton(icon = Icons.Default.RemoveRedEye, text = context.translation["chat_action_menu.preview_button"], modifier = Modifier.clickable {
+ ListButton(icon = Icons.Outlined.RemoveRedEye, text = context.translation["chat_action_menu.preview_button"], modifier = Modifier.clickable {
closeActionMenu()
mediaDownloader.onMessageActionMenu(true)
})
- ListButton(icon = Icons.Default.Download, text = context.translation["chat_action_menu.download_button"], modifier = Modifier.pointerInput(Unit) {
+ ListButton(icon = Icons.Outlined.Download, text = context.translation["chat_action_menu.download_button"], modifier = Modifier.pointerInput(Unit) {
detectTapGestures(
onTap = {
closeActionMenu()
@@ -319,7 +355,7 @@ class NewChatActionMenu : AbstractMenu() {
}
if (context.config.experimental.editMessage.get() && messaging.conversationManager?.isEditMessageSupported() == true) {
- ListButton(icon = Icons.Rounded.Edit, text = context.translation["chat_action_menu.edit_message"], modifier = Modifier.clickable {
+ ListButton(icon = Icons.Outlined.Edit, text = context.translation["chat_action_menu.edit_message"], modifier = Modifier.clickable {
editCurrentMessage(event.view.context) {
context.runOnUiThread {
closeActionMenu()
@@ -329,7 +365,21 @@ class NewChatActionMenu : AbstractMenu() {
}
if (context.config.messaging.messageLogger.globalState == true) {
- ListButton(icon = Icons.Rounded.BookmarkRemove, text = context.translation["chat_action_menu.delete_logged_message_button"], modifier = Modifier.clickable {
+ val chatEdits by rememberAsyncMutableState(defaultValue = null) {
+ context.feature(MessageLogger::class).getChatEdits(
+ messaging.openedConversationUUID.toString(),
+ messaging.lastFocusedMessageId
+ )
+ }
+
+ if (chatEdits != null && chatEdits?.isNotEmpty() == true) {
+ ListButton(icon = Icons.Outlined.History, text = context.translation["chat_action_menu.show_chat_edit_history"], modifier = Modifier.clickable {
+ closeActionMenu()
+ showChatEditHistory(chatEdits!!)
+ })
+ }
+
+ ListButton(icon = Icons.Outlined.BookmarkRemove, text = context.translation["chat_action_menu.delete_logged_message_button"], modifier = Modifier.clickable {
closeActionMenu()
context.executeAsync {
messageLogger.deleteMessage(messaging.openedConversationUUID.toString(), messaging.lastFocusedMessageId)