New features: backup & restore data
This commit is contained in:
parent
6359093ed7
commit
f67b9c6276
8 changed files with 188 additions and 7 deletions
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
24
app/src/main/res/layout/dialog_backup.xml
Normal file
24
app/src/main/res/layout/dialog_backup.xml
Normal 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>
|
||||||
39
app/src/main/res/layout/dialog_restore.xml
Normal file
39
app/src/main/res/layout/dialog_restore.xml
Normal 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>
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue