diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml
index 1c583b2..1f57f40 100644
--- a/mobile/src/main/AndroidManifest.xml
+++ b/mobile/src/main/AndroidManifest.xml
@@ -8,6 +8,11 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.OmniCards">
+
+ 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(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>(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")
+ }
+ }
+}
diff --git a/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt b/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt
index 1eec773..0b67f0d 100644
--- a/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt
+++ b/mobile/src/main/java/ru/omni_devel/cards/DbHelper.kt
@@ -106,4 +106,28 @@ class DbHelper(val context: Context, val factory: SQLiteDatabase.CursorFactory?)
db.close()
}
+
+ fun clearDatabase() {
+ val db = this.writableDatabase
+
+ db.execSQL("DELETE FROM cards")
+ }
+
+ fun addCards(cards: List) {
+ 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()
+ }
}
\ No newline at end of file
diff --git a/mobile/src/main/java/ru/omni_devel/cards/EditCardActivity.kt b/mobile/src/main/java/ru/omni_devel/cards/EditCardActivity.kt
index ab4c21f..b439fd3 100644
--- a/mobile/src/main/java/ru/omni_devel/cards/EditCardActivity.kt
+++ b/mobile/src/main/java/ru/omni_devel/cards/EditCardActivity.kt
@@ -1,6 +1,5 @@
package ru.omni_devel.cards
-import android.app.Activity
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
@@ -23,7 +22,6 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
-import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Switch
@@ -32,12 +30,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.graphics.toColorInt
import com.github.skydoves.colorpicker.compose.ColorEnvelope
diff --git a/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt b/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt
index f8c2d9f..8f57706 100644
--- a/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt
+++ b/mobile/src/main/java/ru/omni_devel/cards/MainActivity.kt
@@ -145,9 +145,22 @@ fun MainPage(
) {
Text("Sync with WearOS")
}
+ FullWidthButton(
+ onClick = {
+ val intent = Intent(context, BackupActivity::class.java)
+
+ context.startActivity(intent)
+
+ isActionBarShowed = false
+ }
+ ) {
+ Text("Backup")
+ }
FullWidthButton(
onClick = {
uriHandler.openUri("https://github.com/omni-devel/OmniCards")
+
+ isActionBarShowed = false
}
) {
Text("Source code")
diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml
index 05c113c..e54dca9 100644
--- a/mobile/src/main/res/values/strings.xml
+++ b/mobile/src/main/res/values/strings.xml
@@ -45,4 +45,5 @@
ShowCardActivity
AddCardActivity
EditCardActivity
+ BackupActivity
\ No newline at end of file