feat: Friend notes backup/restore
This commit is contained in:
@@ -197,6 +197,14 @@ class BridgeService : Service() {
|
||||
remoteSideContext.database.setScopeNotes(id, content)
|
||||
}
|
||||
|
||||
override fun getAllScopeNotes(): Map<String, String> {
|
||||
return remoteSideContext.database.getAllScopeNotes()
|
||||
}
|
||||
|
||||
override fun setAllScopeNotes(notes: Map<String, String>) {
|
||||
remoteSideContext.database.setAllScopeNotes(notes)
|
||||
}
|
||||
|
||||
override fun getScriptingInterface() = remoteSideContext.scriptManager
|
||||
|
||||
override fun getE2eeInterface() = remoteSideContext.e2eeImplementation
|
||||
|
||||
@@ -30,3 +30,27 @@ fun AppDatabase.deleteScopeNotes(id: String) {
|
||||
database.execSQL("DELETE FROM notes WHERE id = ?", arrayOf(id))
|
||||
}
|
||||
}
|
||||
|
||||
fun AppDatabase.getAllScopeNotes(): Map<String, String> {
|
||||
return database.rawQuery("SELECT id, content FROM notes", null).use {
|
||||
val map = mutableMapOf<String, String>()
|
||||
while (it.moveToNext()) {
|
||||
map[it.getString(0)] = it.getString(1)
|
||||
}
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
fun AppDatabase.setAllScopeNotes(notes: Map<String, String>) {
|
||||
executeAsync {
|
||||
database.beginTransaction()
|
||||
try {
|
||||
for ((id, content) in notes) {
|
||||
database.execSQL("INSERT OR REPLACE INTO notes (id, content) VALUES (?, ?)", arrayOf(id, content))
|
||||
}
|
||||
database.setTransactionSuccessful()
|
||||
} finally {
|
||||
database.endTransaction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ import androidx.compose.ui.window.Dialog
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import kotlinx.coroutines.launch
|
||||
import me.rhunk.snapenhance.common.action.EnumAction
|
||||
import me.rhunk.snapenhance.common.bridge.InternalFileHandleType
|
||||
@@ -31,17 +36,15 @@ import me.rhunk.snapenhance.common.ui.ThemeChooserDialog
|
||||
import me.rhunk.snapenhance.common.ui.ThemeMode
|
||||
import me.rhunk.snapenhance.common.ui.ThemePreferences
|
||||
import me.rhunk.snapenhance.common.ui.rememberAsyncMutableState
|
||||
import me.rhunk.snapenhance.storage.getAllScopeNotes
|
||||
import me.rhunk.snapenhance.storage.setAllScopeNotes
|
||||
import me.rhunk.snapenhance.task.UpdateCheckWorker
|
||||
import me.rhunk.snapenhance.ui.manager.Routes
|
||||
import me.rhunk.snapenhance.ui.setup.Requirements
|
||||
import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
|
||||
import me.rhunk.snapenhance.ui.util.AlertDialogs
|
||||
import me.rhunk.snapenhance.ui.util.openFile
|
||||
import me.rhunk.snapenhance.ui.util.saveFile
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.NetworkType
|
||||
import me.rhunk.snapenhance.task.UpdateCheckWorker
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class HomeSettings : Routes.Route() {
|
||||
@@ -426,6 +429,67 @@ class HomeSettings : Routes.Route() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowTitle(title = translation["friend_notes_title"])
|
||||
ShiftedRow {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(5.dp)
|
||||
) {
|
||||
Text(
|
||||
text = translation["friend_notes_description"],
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Button(onClick = {
|
||||
runCatching {
|
||||
val notes = context.database.getAllScopeNotes()
|
||||
if (notes.isEmpty()) {
|
||||
context.shortToast(translation["friend_notes_no_notes_to_backup"])
|
||||
return@runCatching
|
||||
}
|
||||
val json = context.gson.toJson(notes)
|
||||
activityLauncherHelper.saveFile("friend_notes_backup.json", "application/json") { uri ->
|
||||
context.androidContext.contentResolver.openOutputStream(uri.toUri())?.use {
|
||||
it.write(json.toByteArray())
|
||||
}
|
||||
context.shortToast(translation["friend_notes_backup_success"])
|
||||
}
|
||||
}.onFailure {
|
||||
context.log.error("Failed to backup notes", it)
|
||||
context.longToast(translation.format("friend_notes_backup_failure", "error" to (it.localizedMessage ?: "")))
|
||||
}
|
||||
}) {
|
||||
Text(text = translation["backup_button"])
|
||||
}
|
||||
Button(onClick = {
|
||||
runCatching {
|
||||
activityLauncherHelper.openFile("application/json") { uri ->
|
||||
context.androidContext.contentResolver.openInputStream(uri.toUri())?.use {
|
||||
val json = it.reader().readText()
|
||||
val notes = context.gson.fromJson<Map<String, String>>(json, object : com.google.gson.reflect.TypeToken<Map<String, String>>() {}.type)
|
||||
context.database.setAllScopeNotes(notes)
|
||||
context.shortToast(translation["friend_notes_restore_success"])
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
context.log.error("Failed to restore notes", it)
|
||||
context.longToast(translation.format("friend_notes_restore_failure", "error" to (it.localizedMessage ?: "")))
|
||||
}
|
||||
}) {
|
||||
Text(text = translation["restore_button"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowTitle(title = translation["debug_title"])
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
@@ -489,4 +553,4 @@ class HomeSettings : Routes.Route() {
|
||||
Spacer(modifier = Modifier.height(routes.bottomPadding))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,10 @@ interface BridgeInterface {
|
||||
|
||||
oneway void setScopeNotes(String id, String content);
|
||||
|
||||
Map<String, String> getAllScopeNotes();
|
||||
|
||||
oneway void setAllScopeNotes(in Map<String, String> notes);
|
||||
|
||||
IScripting getScriptingInterface();
|
||||
|
||||
E2eeInterface getE2eeInterface();
|
||||
|
||||
@@ -71,7 +71,16 @@
|
||||
"message_logger_summary": "{messageCount} messages\n{storyCount} stories",
|
||||
"export_button": "Export",
|
||||
"clear_button": "Clear",
|
||||
"backup_button": "Backup",
|
||||
"restore_button": "Restore",
|
||||
"view_logger_history_button": "View Logger History",
|
||||
"friend_notes_title": "Friend Notes",
|
||||
"friend_notes_description": "Backup or restore your friend notes.",
|
||||
"friend_notes_backup_success": "Backup successful",
|
||||
"friend_notes_backup_failure": "Failed to backup notes! {error}",
|
||||
"friend_notes_restore_success": "Restore successful",
|
||||
"friend_notes_restore_failure": "Failed to restore notes! {error}",
|
||||
"friend_notes_no_notes_to_backup": "No notes to backup",
|
||||
"updates_title": "Updates",
|
||||
"auto_update_check": "Auto check for updates",
|
||||
"update_check_frequency": "Update check frequency",
|
||||
|
||||
@@ -20,6 +20,14 @@ tasks.register("installTypeScript", org.gradle.api.tasks.Exec::class) {
|
||||
} else {
|
||||
commandLine("npm", "install", "typescript")
|
||||
}
|
||||
doLast {
|
||||
if (!Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
val tscScript = project.file("node_modules/.bin/tsc")
|
||||
if (tscScript.exists()) {
|
||||
tscScript.setExecutable(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("compileTsc", org.gradle.api.tasks.Exec::class) {
|
||||
|
||||
@@ -255,6 +255,10 @@ class BridgeClient(
|
||||
|
||||
fun setScopeNotes(id: String, content: String?) = safeServiceCall { service.setScopeNotes(id, content) }
|
||||
|
||||
fun getAllScopeNotes(): Map<String, String> = safeServiceCall { service.getAllScopeNotes() }
|
||||
|
||||
fun setAllScopeNotes(notes: Map<String, String>) = safeServiceCall { service.setAllScopeNotes(notes) }
|
||||
|
||||
fun getScriptingInterface(): IScripting = safeServiceCall { service.scriptingInterface }
|
||||
|
||||
fun getE2eeInterface(): E2eeInterface = safeServiceCall { service.e2eeInterface }
|
||||
|
||||
Reference in New Issue
Block a user