feat: added backup and restored functionality

This commit is contained in:
2026-06-14 20:21:38 +03:00
parent fba54e0243
commit 52c485af64
6 changed files with 193 additions and 4 deletions
+5
View File
@@ -8,6 +8,11 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.OmniCards"> android:theme="@style/Theme.OmniCards">
<activity
android:name=".BackupActivity"
android:exported="false"
android:label="@string/title_activity_backup"
android:theme="@style/Theme.OmniCards" />
<activity <activity
android:name=".EditCardActivity" android:name=".EditCardActivity"
android:exported="false" android:exported="false"
@@ -0,0 +1,150 @@
package ru.omni_devel.cards
import android.net.Uri
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.LocalActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import kotlinx.serialization.json.Json
import ru.omni_devel.cards.ui.theme.OmniCardsTheme
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import kotlinx.serialization.decodeFromString
class BackupActivity : ComponentActivity() {
private lateinit var db: DbHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
db = DbHelper(this, null)
setContent {
OmniCardsTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
BackupPage(innerPadding = innerPadding, getDbFun = {
return@BackupPage db
})
}
}
}
}
}
@Composable
fun BackupPage(innerPadding: PaddingValues, getDbFun: () -> DbHelper) {
val context = LocalContext.current
val activityContext = LocalActivity.current
val showImportDialog = rememberSaveable { mutableStateOf(false) }
var pendingUri by rememberSaveable { mutableStateOf<Uri?>(null) }
val createBackupLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.CreateDocument("application/json")
) { uri ->
uri?.let {
try {
val jsonData = Json.encodeToString(getDbFun().getCards())
context.contentResolver.openOutputStream(it)?.use { outputStream ->
outputStream.write(jsonData.toByteArray())
Toast.makeText(context, "Backup successfully saved", Toast.LENGTH_SHORT).show()
activityContext!!.finish()
}
} catch (e: Exception) {
Toast.makeText(context, "Failed to save backup: $e", Toast.LENGTH_LONG).show()
}
}
}
val restoreFromBackupLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetContent()
) { uri ->
uri?.let {
pendingUri = it
showImportDialog.value = true
}
}
if (showImportDialog.value) {
AlertDialog(
title = { Text("Restore backup") },
text = { Text("This will replace existing cards by data from backup file. Continue?") },
confirmButton = {
TextButton(
onClick = {
showImportDialog.value = false
pendingUri?.let { uri ->
try {
context.contentResolver.openInputStream(uri)?.use { stream ->
val json = stream.bufferedReader().use { it.readText() }
val cards = Json.decodeFromString<List<CardInfo>>(json)
val db = getDbFun()
db.clearDatabase()
db.addCards(cards)
Toast.makeText(context, "Data successfully restored from backup", Toast.LENGTH_SHORT).show()
activityContext!!.finish()
}
} catch (e: Exception) {
Toast.makeText(context, "Failed to restore data from backup: $e", Toast.LENGTH_LONG).show()
}
}
}
) {
Text("Yes")
}
},
dismissButton = {
TextButton(
onClick = {
showImportDialog.value = false
}
) {
Text("No")
}
},
onDismissRequest = { showImportDialog.value = false },
)
}
Page("Backup", innerPadding) {
FullWidthButton(
onClick = {
createBackupLauncher.launch("omnicards-backup.json")
}
) {
Text("Backup to file")
}
FullWidthButton(
onClick = {
restoreFromBackupLauncher.launch("application/json")
}
) {
Text("Restore from file")
}
}
}
@@ -106,4 +106,28 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
db.close() db.close()
} }
fun clearDatabase() {
val db = this.writableDatabase
db.execSQL("DELETE FROM cards")
}
fun addCards(cards: List<CardInfo>) {
val db = this.writableDatabase
for (card in cards) {
val values = ContentValues()
values.put("id", card.id)
values.put("name", card.name)
values.put("codeValue", card.codeValue)
values.put("codeType", card.codeType.name)
values.put("color", card.color)
db.insert("cards", null, values)
}
db.close()
}
} }
@@ -1,6 +1,5 @@
package ru.omni_devel.cards package ru.omni_devel.cards
import android.app.Activity
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
@@ -23,7 +22,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
@@ -32,12 +30,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.graphics.toColorInt import androidx.core.graphics.toColorInt
import com.github.skydoves.colorpicker.compose.ColorEnvelope import com.github.skydoves.colorpicker.compose.ColorEnvelope
@@ -145,9 +145,22 @@ fun MainPage(
) { ) {
Text("Sync with WearOS") Text("Sync with WearOS")
} }
FullWidthButton(
onClick = {
val intent = Intent(context, BackupActivity::class.java)
context.startActivity(intent)
isActionBarShowed = false
}
) {
Text("Backup")
}
FullWidthButton( FullWidthButton(
onClick = { onClick = {
uriHandler.openUri("https://github.com/omni-devel/OmniCards") uriHandler.openUri("https://github.com/omni-devel/OmniCards")
isActionBarShowed = false
} }
) { ) {
Text("Source code") Text("Source code")
+1
View File
@@ -45,4 +45,5 @@
<string name="title_activity_show_card">ShowCardActivity</string> <string name="title_activity_show_card">ShowCardActivity</string>
<string name="title_activity_add_card">AddCardActivity</string> <string name="title_activity_add_card">AddCardActivity</string>
<string name="title_activity_edit_card">EditCardActivity</string> <string name="title_activity_edit_card">EditCardActivity</string>
<string name="title_activity_backup">BackupActivity</string>
</resources> </resources>