New features: backup & restore data

This commit is contained in:
Super12138 2024-01-21 12:47:45 +08:00
parent 6359093ed7
commit f67b9c6276
8 changed files with 188 additions and 7 deletions

View file

@ -69,6 +69,8 @@ dependencies {
implementation("androidx.room:room-ktx:2.6.1") implementation("androidx.room:room-ktx:2.6.1")
annotationProcessor("androidx.room:room-compiler:2.6.1") annotationProcessor("androidx.room:room-compiler:2.6.1")
ksp("androidx.room:room-compiler:2.6.1") ksp("androidx.room:room-compiler:2.6.1")
// Gson
implementation("com.google.code.gson:gson:2.10.1")
// Test // Test
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.ext:junit:1.1.5")

View file

@ -3,14 +3,29 @@ package cn.super12138.todo.views.settings
import android.os.Bundle import android.os.Bundle
import android.os.Process import android.os.Process
import android.view.WindowManager import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat import androidx.preference.SwitchPreferenceCompat
import cn.super12138.todo.R import cn.super12138.todo.R
import cn.super12138.todo.databinding.ActivitySettingsBinding import cn.super12138.todo.databinding.ActivitySettingsBinding
import cn.super12138.todo.databinding.DialogBackupBinding
import cn.super12138.todo.databinding.DialogRestoreBinding
import cn.super12138.todo.logic.Repository
import cn.super12138.todo.logic.dao.ToDoRoom
import cn.super12138.todo.views.BaseActivity import cn.super12138.todo.views.BaseActivity
import cn.super12138.todo.views.progress.ProgressFragmentViewModel
import cn.super12138.todo.views.todo.ToDoFragmentViewModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.launch
import kotlin.system.exitProcess import kotlin.system.exitProcess
class SettingsActivity : BaseActivity() { class SettingsActivity : BaseActivity() {
@ -19,7 +34,6 @@ class SettingsActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivitySettingsBinding.inflate(layoutInflater) binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
@ -37,8 +51,15 @@ class SettingsActivity : BaseActivity() {
} }
class SettingsFragment : PreferenceFragmentCompat() { class SettingsFragment : PreferenceFragmentCompat() {
private lateinit var backupBinding: DialogBackupBinding
private lateinit var restoreBinding: DialogRestoreBinding
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey) setPreferencesFromResource(R.xml.preferences, rootKey)
val gson = Gson()
val progressViewModel =
ViewModelProvider(requireActivity()).get(ProgressFragmentViewModel::class.java)
val todoViewModel =
ViewModelProvider(requireActivity()).get(ToDoFragmentViewModel::class.java)
findPreference<ListPreference>("dark_mode")?.apply { findPreference<ListPreference>("dark_mode")?.apply {
setOnPreferenceChangeListener { preference, newValue -> setOnPreferenceChangeListener { preference, newValue ->
@ -76,6 +97,79 @@ class SettingsActivity : BaseActivity() {
true true
} }
} }
findPreference<Preference>("backupDB")?.apply {
setOnPreferenceClickListener {
lifecycleScope.launch {
val data = Repository.getAll()
val jsonData = gson.toJson(data)
backupBinding = DialogBackupBinding.inflate(layoutInflater)
backupBinding.jsonOutput.text = jsonData
activity?.let {
MaterialAlertDialogBuilder(it)
.setTitle(R.string.export_data)
.setView(backupBinding.root)
.setPositiveButton(R.string.ok, null)
.show()
}
}
true
}
}
findPreference<Preference>("restoreDB")?.apply {
setOnPreferenceClickListener {
restoreBinding = DialogRestoreBinding.inflate(layoutInflater)
activity?.let { it1 ->
val dialog = MaterialAlertDialogBuilder(it1)
.setTitle(R.string.restore_data)
.setView(restoreBinding.root)
.setPositiveButton(R.string.ok, null)
.setNegativeButton(R.string.cancel, null)
.show()
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val jsonInput = restoreBinding.jsonInput.editText?.text.toString()
if (jsonInput.isEmpty()) {
restoreBinding.jsonInput.error =
getString(R.string.please_paste_data)
return@setOnClickListener
}
lifecycleScope.launch {
if (restoreBinding.overwriteData.isChecked) {
Repository.deleteAll()
}
try {
val listType = object : TypeToken<List<ToDoRoom>>() {}.type
val taskList: List<ToDoRoom> =
gson.fromJson(jsonInput, listType)
taskList.forEach { Repository.insert(it) }
dialog.dismiss()
MaterialAlertDialogBuilder(it1)
.setTitle(R.string.restore_successful)
.setMessage(R.string.restore_need_restart_app)
.setPositiveButton(R.string.ok) { _, _ ->
Process.killProcess(Process.myPid())
exitProcess(10)
}
.setNegativeButton(R.string.cancel, null)
.setCancelable(false)
.show()
} catch (e: JsonSyntaxException) {
restoreBinding.jsonInput.error =
getString(R.string.json_data_incorrect)
} catch (e: Exception) {
restoreBinding.jsonInput.error =
getString(R.string.restore_failed)
}
}
}
}
true
}
}
} }
} }
} }

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingStart="25dp"
android:paddingTop="5dp"
android:paddingEnd="25dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/backup_tips" />
<TextView
android:id="@+id/json_output"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:fontFamily="monospace"
android:textColor="?android:attr/textColorPrimary"
android:textIsSelectable="true" />
</LinearLayout>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="25dp"
android:paddingTop="5dp"
android:paddingEnd="25dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/restore_tips" />
<CheckBox
android:id="@+id/overwrite_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/overwrite_data" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/json_input"
style="?attr/textInputFilledStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/paste_data"
app:endIconMode="clear_text"
app:errorEnabled="true"
app:helperText=" "
app:helperTextEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View file

@ -2,7 +2,8 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:animateLayoutChanges="true">
<com.google.android.material.progressindicator.CircularProgressIndicator <com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progress_bar" android:id="@+id/progress_bar"
@ -11,16 +12,15 @@
android:layout_marginLeft="15dp" android:layout_marginLeft="15dp"
android:layout_marginRight="15dp" android:layout_marginRight="15dp"
android:max="100" android:max="100"
app:indicatorSize="160dp"
app:indicatorTrackGapSize="10dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:indicatorSize="160dp"
app:trackColor="?attr/colorSurfaceContainerHighest" app:trackColor="?attr/colorSurfaceContainerHighest"
app:trackCornerRadius="25dp" app:trackCornerRadius="25dp"
app:trackThickness="10dp" app:trackThickness="10dp" />
app:showDelay="150"
app:indicatorTrackGapSize="10dp"/>
<LinearLayout <LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -49,4 +49,15 @@
<string name="backup_summary">备份待办数据库</string> <string name="backup_summary">备份待办数据库</string>
<string name="restore">恢复数据库</string> <string name="restore">恢复数据库</string>
<string name="restore_summary">恢复先前备份过的待办数据库</string> <string name="restore_summary">恢复先前备份过的待办数据库</string>
<string name="backup_tips">下方是数据库文件,请全选复制以导出</string>
<string name="restore_tips">在下方粘贴已经导出的数据库</string>
<string name="overwrite_data">覆盖原有数据</string>
<string name="paste_data">粘贴导出的数据</string>
<string name="export_data">导出数据</string>
<string name="restore_data">恢复数据</string>
<string name="please_paste_data">请粘贴数据</string>
<string name="restore_successful">数据恢复成功</string>
<string name="restore_need_restart_app">需要重启应用以应用恢复的数据</string>
<string name="json_data_incorrect">JSON 数据格式错误</string>
<string name="restore_failed">恢复失败,请检查是否重复恢复同一次备份的数据</string>
</resources> </resources>

View file

@ -49,4 +49,15 @@
<string name="backup_summary">Backup to-do database</string> <string name="backup_summary">Backup to-do database</string>
<string name="restore">Restore database</string> <string name="restore">Restore database</string>
<string name="restore_summary">Restore the previously backed up to-do database</string> <string name="restore_summary">Restore the previously backed up to-do database</string>
<string name="backup_tips">The following is the database file, please select all and copy to export.</string>
<string name="restore_tips">Paste the exported database below.</string>
<string name="overwrite_data">Overwrite old data</string>
<string name="paste_data">Paste exported data</string>
<string name="export_data">Export data</string>
<string name="restore_data">Restore data</string>
<string name="please_paste_data">Please paste the data</string>
<string name="restore_successful">Data restore successful</string>
<string name="restore_need_restart_app">Need to restart the app to apply the recovered data</string>
<string name="json_data_incorrect">JSON data format is incorrect</string>
<string name="restore_failed">Restore failed, please check if you are attempting to restore the same backup data again.</string>
</resources> </resources>

View file

@ -63,7 +63,7 @@
</style> </style>
<style name="ToDo.M3AlertDialog" parent="ThemeOverlay.Material3.MaterialAlertDialog"> <style name="ToDo.M3AlertDialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
<item name="dialogCornerRadius">28dp</item> <item name="dialogCornerRadius">30dp</item>
<item name="cornerFamily">rounded</item> <item name="cornerFamily">rounded</item>
<item name="android:colorBackground">?attr/colorSurfaceContainerHigh</item> <item name="android:colorBackground">?attr/colorSurfaceContainerHigh</item>
<item name="android:layout">@layout/m3_alert_dialog</item> <item name="android:layout">@layout/m3_alert_dialog</item>