commit 15c56b705f99c27905d5719285ae7955858ac2ab
parent 780d5b98588453fe51e8b77203210a79283cff89
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Wed, 22 Nov 2023 20:41:03 +0100
feat(app/settings): message logger export & clear
Diffstat:
5 files changed, 156 insertions(+), 28 deletions(-)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt b/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt
@@ -21,8 +21,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import me.rhunk.snapenhance.bridge.BridgeService
import me.rhunk.snapenhance.common.BuildConfig
+import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.common.bridge.wrapper.MappingsWrapper
+import me.rhunk.snapenhance.common.bridge.wrapper.MessageLoggerWrapper
import me.rhunk.snapenhance.common.config.ModConfig
import me.rhunk.snapenhance.e2ee.E2EEImplementation
import me.rhunk.snapenhance.messaging.ModDatabase
@@ -67,6 +69,7 @@ class RemoteSideContext(
val scriptManager = RemoteScriptManager(this)
val settingsOverlay = SettingsOverlay(this)
val e2eeImplementation = E2EEImplementation(this)
+ val messageLogger by lazy { MessageLoggerWrapper(androidContext.getDatabasePath(BridgeFileType.MESSAGE_LOGGER_DATABASE.fileName)) }
//used to load bitmoji selfies and download previews
val imageLoader by lazy {
@@ -104,6 +107,7 @@ class RemoteSideContext(
modDatabase.init()
streaksReminder.init()
scriptManager.init()
+ messageLogger.init()
}.onFailure {
log.error("Failed to load RemoteSideContext", it)
}
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/bridge/BridgeService.kt b/app/src/main/kotlin/me/rhunk/snapenhance/bridge/BridgeService.kt
@@ -20,7 +20,6 @@ import me.rhunk.snapenhance.download.DownloadProcessor
import kotlin.system.measureTimeMillis
class BridgeService : Service() {
- private lateinit var messageLoggerWrapper: MessageLoggerWrapper
private lateinit var remoteSideContext: RemoteSideContext
lateinit var syncCallback: SyncCallback
var messagingBridge: MessagingBridge? = null
@@ -38,7 +37,6 @@ class BridgeService : Service() {
remoteSideContext.apply {
bridgeService = this@BridgeService
}
- messageLoggerWrapper = MessageLoggerWrapper(getDatabasePath(BridgeFileType.MESSAGE_LOGGER_DATABASE.fileName)).also { it.init() }
return BridgeBinder()
}
@@ -180,7 +178,7 @@ class BridgeService : Service() {
override fun getScriptingInterface() = remoteSideContext.scriptManager
override fun getE2eeInterface() = remoteSideContext.e2eeImplementation
- override fun getMessageLogger() = messageLoggerWrapper
+ override fun getMessageLogger() = remoteSideContext.messageLogger
override fun registerMessagingBridge(bridge: MessagingBridge) {
messagingBridge = bridge
}
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/home/HomeSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/home/HomeSection.kt
@@ -271,7 +271,7 @@ class HomeSection : Section() {
homeSubSection.LogsSection()
}
composable(SETTINGS_SECTION_ROUTE) {
- SettingsSection().also { it.context = context }.Content()
+ SettingsSection(activityLauncherHelper).also { it.context = context }.Content()
}
}
}
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/home/SettingsSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/home/SettingsSection.kt
@@ -6,27 +6,28 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.OpenInNew
-import androidx.compose.material3.Icon
-import androidx.compose.material3.IconButton
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
+import androidx.core.net.toUri
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
import me.rhunk.snapenhance.common.Constants
import me.rhunk.snapenhance.common.action.EnumAction
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
import me.rhunk.snapenhance.ui.manager.Section
+import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
import me.rhunk.snapenhance.ui.util.AlertDialogs
+import me.rhunk.snapenhance.ui.util.saveFile
-class SettingsSection : Section() {
+class SettingsSection(
+ private val activityLauncherHelper: ActivityLauncherHelper
+) : Section() {
private val dialogs by lazy { AlertDialogs(context.translation) }
@Composable
@@ -59,7 +60,7 @@ class SettingsSection : Section() {
}
}
- Row(
+ ShiftedRow(
modifier = Modifier
.fillMaxWidth()
.height(65.dp)
@@ -87,6 +88,22 @@ class SettingsSection : Section() {
}
@Composable
+ private fun ShiftedRow(
+ modifier: Modifier = Modifier,
+ horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
+ verticalAlignment: Alignment.Vertical = Alignment.Top,
+ content: @Composable RowScope.() -> Unit
+ ) {
+ Row(
+ modifier = modifier.padding(start = 26.dp),
+ horizontalArrangement = horizontalArrangement,
+ verticalAlignment = verticalAlignment
+ ) { content(this) }
+ }
+
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Composable
override fun Content() {
Column(
modifier = Modifier
@@ -99,16 +116,106 @@ class SettingsSection : Section() {
launchActionIntent(enumAction)
}
}
+ RowTitle(title = "Message Logger")
+ ShiftedRow {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ var storedMessagesCount by remember { mutableIntStateOf(0) }
+ LaunchedEffect(Unit) {
+ withContext(Dispatchers.IO) {
+ storedMessagesCount = context.messageLogger.getStoredMessageCount()
+ }
+ }
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(5.dp)
+ ) {
+ Text(text = "$storedMessagesCount messages", modifier = Modifier.weight(1f))
+ Button(onClick = {
+ runCatching {
+ activityLauncherHelper.saveFile("message_logger.db", "application/octet-stream") { uri ->
+ context.androidContext.contentResolver.openOutputStream(uri.toUri())?.use { outputStream ->
+ context.messageLogger.databaseFile.inputStream().use { inputStream ->
+ inputStream.copyTo(outputStream)
+ }
+ }
+ }
+ }.onFailure {
+ context.log.error("Failed to export database", it)
+ context.longToast("Failed to export database! ${it.localizedMessage}")
+ }
+ }) {
+ Text(text = "Export")
+ }
+ Button(onClick = {
+ runCatching {
+ context.messageLogger.clearMessages()
+ storedMessagesCount = 0
+ }.onFailure {
+ context.log.error("Failed to clear messages", it)
+ context.longToast("Failed to clear messages! ${it.localizedMessage}")
+ }.onSuccess {
+ context.shortToast("Done!")
+ }
+ }) {
+ Text(text = "Clear")
+ }
+ }
+ }
+ }
- RowTitle(title = "Clear Files")
- BridgeFileType.entries.forEach { fileType ->
- RowAction(title = fileType.displayName, requireConfirmation = true) {
+ RowTitle(title = "Clear App Files")
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ var selectedFileType by remember { mutableStateOf(BridgeFileType.entries.first()) }
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .padding(start = 26.dp)
+ ) {
+ var expanded by remember { mutableStateOf(false) }
+
+ ExposedDropdownMenuBox(
+ expanded = expanded,
+ onExpandedChange = { expanded = it },
+ modifier = Modifier.fillMaxWidth(0.8f)
+ ) {
+ TextField(
+ value = selectedFileType.displayName,
+ onValueChange = {},
+ readOnly = true,
+ modifier = Modifier.menuAnchor()
+ )
+
+ ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
+ BridgeFileType.entries.forEach { fileType ->
+ DropdownMenuItem(onClick = {
+ expanded = false
+ selectedFileType = fileType
+ }, text = {
+ Text(text = fileType.displayName)
+ })
+ }
+ }
+ }
+ }
+ Button(onClick = {
runCatching {
- fileType.resolve(context.androidContext).delete()
- context.longToast("Deleted ${fileType.displayName}!")
+ selectedFileType.resolve(context.androidContext).delete()
}.onFailure {
- context.longToast("Failed to delete ${fileType.displayName}!")
+ context.log.error("Failed to clear file", it)
+ context.longToast("Failed to clear file! ${it.localizedMessage}")
+ }.onSuccess {
+ context.shortToast("Done!")
}
+ }) {
+ Text(text = "Clear")
}
}
}
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/MessageLoggerWrapper.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/MessageLoggerWrapper.kt
@@ -2,15 +2,18 @@ package me.rhunk.snapenhance.common.bridge.wrapper
import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase
+import kotlinx.coroutines.*
import me.rhunk.snapenhance.bridge.MessageLoggerInterface
import me.rhunk.snapenhance.common.util.SQLiteDatabaseHelper
import java.io.File
import java.util.UUID
class MessageLoggerWrapper(
- private val databaseFile: File
+ val databaseFile: File
): MessageLoggerInterface.Stub() {
private var _database: SQLiteDatabase? = null
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val coroutineScope = CoroutineScope(Dispatchers.IO.limitedParallelism(1))
private val database get() = synchronized(this) {
_database?.takeIf { it.isOpen } ?: run {
@@ -74,19 +77,35 @@ class MessageLoggerWrapper(
if (state) {
return false
}
- database.insert("messages", null, ContentValues().apply {
- put("conversation_id", conversationId)
- put("message_id", messageId)
- put("message_data", serializedMessage)
- })
+ runBlocking {
+ withContext(coroutineScope.coroutineContext) {
+ database.insert("messages", null, ContentValues().apply {
+ put("conversation_id", conversationId)
+ put("message_id", messageId)
+ put("message_data", serializedMessage)
+ })
+ }
+ }
return true
}
fun clearMessages() {
- database.execSQL("DELETE FROM messages")
+ coroutineScope.launch {
+ database.execSQL("DELETE FROM messages")
+ }
+ }
+
+ fun getStoredMessageCount(): Int {
+ val cursor = database.rawQuery("SELECT COUNT(*) FROM messages", null)
+ cursor.moveToFirst()
+ val count = cursor.getInt(0)
+ cursor.close()
+ return count
}
override fun deleteMessage(conversationId: String, messageId: Long) {
- database.execSQL("DELETE FROM messages WHERE conversation_id = ? AND message_id = ?", arrayOf(conversationId, messageId.toString()))
+ coroutineScope.launch {
+ database.execSQL("DELETE FROM messages WHERE conversation_id = ? AND message_id = ?", arrayOf(conversationId, messageId.toString()))
+ }
}
}
\ No newline at end of file