From c1849e9d750023feb3a9f11aaf215d09a8a44ba5 Mon Sep 17 00:00:00 2001 From: Akilesh Date: Tue, 28 Jan 2020 15:19:23 +0530 Subject: [PATCH] [Beta]Backup & restore accents Backup is stored in the internal storage of the device. TODO: Backup app db, support Oreo --- app/src/main/java/app/akilesh/qacc/Const.kt | 56 +++--- .../java/app/akilesh/qacc/ui/MainActivity.kt | 20 +-- .../qacc/ui/adapter/BackupListAdapter.kt | 49 ++++++ .../ui/fragments/BackupRestoreFragment.kt | 166 ++++++++++++++++++ .../akilesh/qacc/ui/fragments/HomeFragment.kt | 6 +- .../qacc/ui/fragments/SettingsFragment.kt | 10 ++ .../java/app/akilesh/qacc/utils/AppUtils.kt | 12 +- ...peToDeleteCallback.kt => SwipeToDelete.kt} | 2 +- .../qacc/viewmodel/BackupFileViewModel.kt | 11 ++ .../main/res/drawable/ic_outline_backup.xml | 10 ++ .../main/res/drawable/ic_outline_pageview.xml | 10 ++ .../ic_round_settings_backup_restore.xml | 10 ++ .../res/layout/backup_restore_fragment.xml | 95 ++++++++++ .../res/layout/recyclerview_item_backups.xml | 62 +++++++ app/src/main/res/navigation/nav_graph.xml | 5 + app/src/main/res/values-de-rDE/strings.xml | 9 +- app/src/main/res/values-zh-rCN/strings.xml | 8 + app/src/main/res/values/strings.xml | 9 + app/src/main/res/xml/root_preferences.xml | 6 + 19 files changed, 508 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/app/akilesh/qacc/ui/adapter/BackupListAdapter.kt create mode 100644 app/src/main/java/app/akilesh/qacc/ui/fragments/BackupRestoreFragment.kt rename app/src/main/java/app/akilesh/qacc/utils/{SwipeToDeleteCallback.kt => SwipeToDelete.kt} (98%) create mode 100644 app/src/main/java/app/akilesh/qacc/viewmodel/BackupFileViewModel.kt create mode 100644 app/src/main/res/drawable/ic_outline_backup.xml create mode 100644 app/src/main/res/drawable/ic_outline_pageview.xml create mode 100644 app/src/main/res/drawable/ic_round_settings_backup_restore.xml create mode 100644 app/src/main/res/layout/backup_restore_fragment.xml create mode 100644 app/src/main/res/layout/recyclerview_item_backups.xml diff --git a/app/src/main/java/app/akilesh/qacc/Const.kt b/app/src/main/java/app/akilesh/qacc/Const.kt index ac65e53..a657e95 100644 --- a/app/src/main/java/app/akilesh/qacc/Const.kt +++ b/app/src/main/java/app/akilesh/qacc/Const.kt @@ -4,42 +4,43 @@ import android.content.Context import android.os.Build import android.os.Build.VERSION.SDK_INT import android.os.Build.VERSION_CODES.Q +import android.os.Environment.DIRECTORY_DOCUMENTS import app.akilesh.qacc.model.Colour import com.topjohnwu.superuser.Shell object Const { - private lateinit var context : Context + lateinit var contextConst : Context fun setContext(appContext: Context) { - context = appContext + contextConst = appContext } //Credits to AEX object Colors { val presets = listOf( - Colour("#FFC107", context.getString(R.string.amber)), - Colour("#448AFF", context.getString(R.string.blue)), - Colour("#607D8B", context.getString(R.string.blue_grey)), - Colour("#795548", context.getString(R.string.brown)), - Colour("#FF1744", context.getString(R.string.candy_red)), - Colour("#00BCD4", context.getString(R.string.cyan)), - Colour("#FF5722", context.getString(R.string.deep_orange)), - Colour("#7C4DFF", context.getString(R.string.deep_purple)), - Colour("#47AE84", context.getString(R.string.elegant_green)), - Colour("#21EF8B", context.getString(R.string.extended_green)), - Colour("#9E9E9E", context.getString(R.string.grey)), - Colour("#536DFE", context.getString(R.string.indigo)), - Colour("#9ABC98", context.getString(R.string.jade_green)), - Colour("#03A9F4", context.getString(R.string.light_blue)), - Colour("#8BC34A", context.getString(R.string.light_green)), - Colour("#CDDC39", context.getString(R.string.lime)), - Colour("#FF9800", context.getString(R.string.orange)), - Colour("#A1B6ED", context.getString(R.string.pale_blue)), - Colour("#F05361", context.getString(R.string.pale_red)), - Colour("#FF4081", context.getString(R.string.pink)), - Colour("#FF5252", context.getString(R.string.red)), - Colour("#009688", context.getString(R.string.teal)), - Colour("#FFEB3B", context.getString(R.string.yellove)) + Colour("#FFC107", contextConst.getString(R.string.amber)), + Colour("#448AFF", contextConst.getString(R.string.blue)), + Colour("#607D8B", contextConst.getString(R.string.blue_grey)), + Colour("#795548", contextConst.getString(R.string.brown)), + Colour("#FF1744", contextConst.getString(R.string.candy_red)), + Colour("#00BCD4", contextConst.getString(R.string.cyan)), + Colour("#FF5722", contextConst.getString(R.string.deep_orange)), + Colour("#7C4DFF", contextConst.getString(R.string.deep_purple)), + Colour("#47AE84", contextConst.getString(R.string.elegant_green)), + Colour("#21EF8B", contextConst.getString(R.string.extended_green)), + Colour("#9E9E9E", contextConst.getString(R.string.grey)), + Colour("#536DFE", contextConst.getString(R.string.indigo)), + Colour("#9ABC98", contextConst.getString(R.string.jade_green)), + Colour("#03A9F4", contextConst.getString(R.string.light_blue)), + Colour("#8BC34A", contextConst.getString(R.string.light_green)), + Colour("#CDDC39", contextConst.getString(R.string.lime)), + Colour("#FF9800", contextConst.getString(R.string.orange)), + Colour("#A1B6ED", contextConst.getString(R.string.pale_blue)), + Colour("#F05361", contextConst.getString(R.string.pale_red)), + Colour("#FF4081", contextConst.getString(R.string.pink)), + Colour("#FF5252", contextConst.getString(R.string.red)), + Colour("#009688", contextConst.getString(R.string.teal)), + Colour("#FFEB3B", contextConst.getString(R.string.yellove)) ) } @@ -53,10 +54,11 @@ object Const { const val githubReleases = "$githubRepo/releases/latest" } - object Module { - private const val modPath = "/data/adb/modules/qacc-mobile" + object Paths { + const val modPath = "/data/adb/modules/qacc-mobile" val overlayPath = if (SDK_INT == Q) "$modPath/system/product/overlay" else "$modPath/system/vendor/overlay" + val backupFolder = "/sdcard/${DIRECTORY_DOCUMENTS}/${contextConst.getString(R.string.app_name_short)}/backups" } const val prefix = "com.android.theme.color.custom." diff --git a/app/src/main/java/app/akilesh/qacc/ui/MainActivity.kt b/app/src/main/java/app/akilesh/qacc/ui/MainActivity.kt index 551d549..d7d4cd7 100644 --- a/app/src/main/java/app/akilesh/qacc/ui/MainActivity.kt +++ b/app/src/main/java/app/akilesh/qacc/ui/MainActivity.kt @@ -9,10 +9,10 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.navOptions import app.akilesh.qacc.Const.getAssetFiles import app.akilesh.qacc.R import app.akilesh.qacc.databinding.ActivityMainBinding +import app.akilesh.qacc.utils.AppUtils.navAnim import app.akilesh.qacc.utils.DownloadUtils.download import app.akilesh.qacc.viewmodel.InstallApkViewModel import com.github.javiersantos.appupdater.AppUpdaterUtils @@ -60,24 +60,14 @@ class MainActivity: AppCompatActivity() { } } - val navOptions = navOptions { - anim { - // Animations from Android 10 - enter = R.anim.fragment_enter - exit = R.anim.fragment_exit - popEnter = R.anim.fragment_enter_pop - popExit = R.anim.fragment_exit_pop - } - } - binding.xFab.setOnClickListener { - navController.navigate(R.id.color_picker, null, navOptions) + navController.navigate(R.id.color_picker, null, navAnim) } binding.bottomAppBar.setOnMenuItemClickListener { when(it.itemId) { - R.id.settings -> navController.navigate(R.id.settings, null, navOptions) - R.id.info -> navController.navigate(R.id.info, null, navOptions) + R.id.settings -> navController.navigate(R.id.settings, null, navAnim) + R.id.info -> navController.navigate(R.id.info, null, navAnim) } true } @@ -87,7 +77,7 @@ class MainActivity: AppCompatActivity() { * May not be the correct way, but convenient. */ binding.bottomAppBar.setNavigationOnClickListener { - navController.navigate(R.id.home, null, navOptions) + navController.navigate(R.id.home, null, navAnim) } copyAssets() diff --git a/app/src/main/java/app/akilesh/qacc/ui/adapter/BackupListAdapter.kt b/app/src/main/java/app/akilesh/qacc/ui/adapter/BackupListAdapter.kt new file mode 100644 index 0000000..14249f0 --- /dev/null +++ b/app/src/main/java/app/akilesh/qacc/ui/adapter/BackupListAdapter.kt @@ -0,0 +1,49 @@ +package app.akilesh.qacc.ui.adapter + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.AppCompatImageView +import androidx.recyclerview.widget.RecyclerView +import app.akilesh.qacc.R +import com.google.android.material.textview.MaterialTextView + +class BackupListAdapter internal constructor( + context: Context, + private var filesList: MutableList, + val onClick : (String) -> Unit +) : RecyclerView.Adapter() { + + private val inflater: LayoutInflater = LayoutInflater.from(context) + + inner class BackupsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val file: MaterialTextView = itemView.findViewById(R.id.backup_file) + val viewContents: AppCompatImageView = itemView.findViewById(R.id.view_content) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BackupsViewHolder { + val itemView = inflater.inflate(R.layout.recyclerview_item_backups, parent, false) + return BackupsViewHolder(itemView) + } + + override fun onBindViewHolder(holder: BackupsViewHolder, position: Int) { + val file = filesList[position] + holder.file.text = file.removeSuffix(".tar.gz").replace('-', ' ') + holder.viewContents.setOnClickListener { onClick(file) } + } + + internal fun setFiles(files: MutableList) { + this.filesList = files + notifyDataSetChanged() + } + + internal fun getFileAndRemoveAt(position: Int): String { + val current = filesList[position] + filesList.removeAt(position) + notifyItemRemoved(position) + return current + } + + override fun getItemCount() = filesList.size +} diff --git a/app/src/main/java/app/akilesh/qacc/ui/fragments/BackupRestoreFragment.kt b/app/src/main/java/app/akilesh/qacc/ui/fragments/BackupRestoreFragment.kt new file mode 100644 index 0000000..fa87e6c --- /dev/null +++ b/app/src/main/java/app/akilesh/qacc/ui/fragments/BackupRestoreFragment.kt @@ -0,0 +1,166 @@ +package app.akilesh.qacc.ui.fragments + +import android.annotation.SuppressLint +import android.app.Activity.RESULT_OK +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import app.akilesh.qacc.Const.Paths.backupFolder +import app.akilesh.qacc.Const.Paths.modPath +import app.akilesh.qacc.Const.Paths.overlayPath +import app.akilesh.qacc.R +import app.akilesh.qacc.databinding.BackupRestoreFragmentBinding +import app.akilesh.qacc.databinding.ColorPreviewBinding +import app.akilesh.qacc.databinding.DialogTitleBinding +import app.akilesh.qacc.model.Colour +import app.akilesh.qacc.ui.adapter.BackupListAdapter +import app.akilesh.qacc.ui.adapter.ColorListAdapter +import app.akilesh.qacc.utils.AppUtils.showSnackbar +import app.akilesh.qacc.utils.SwipeToDelete +import app.akilesh.qacc.viewmodel.BackupFileViewModel +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.topjohnwu.superuser.Shell +import java.io.File +import java.io.FileInputStream +import java.util.* + +class BackupRestoreFragment: Fragment() { + + private lateinit var binding: BackupRestoreFragmentBinding + private lateinit var model: BackupFileViewModel + private val busyBox = "/data/adb/magisk/busybox" + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = BackupRestoreFragmentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.newBackup.setOnClickListener { createBackup() } + binding.restore.setOnClickListener { restore() } + + val adapter = BackupListAdapter(context!!, getBackupFiles()) { file -> + val colorPreviewBinding = ColorPreviewBinding.inflate(layoutInflater) + val dialogTitleBinding = DialogTitleBinding.inflate(layoutInflater) + dialogTitleBinding.titleText.text = String.format(resources.getString(R.string.backup_contents)) + dialogTitleBinding.titleIcon.setImageResource(R.drawable.ic_palette_24dp) + + val contents = getBackupContents(file) + contents.removeIf { it == "./" } + contents.replaceAll { s -> s.removePrefix("./hex").removePrefix("_").removeSuffix(".apk").substringBefore('_') } + Log.d("contents", contents.toString()) + + val accents: List = contents.map { Colour("#$it", getString(R.string.hex_code)) } + val adapter = ColorListAdapter(context!!, accents) {} + + colorPreviewBinding.recyclerViewColor.adapter = adapter + colorPreviewBinding.recyclerViewColor.layoutManager = LinearLayoutManager(context) + + MaterialAlertDialogBuilder(context) + .setCustomTitle(dialogTitleBinding.root) + .setView(colorPreviewBinding.root) + .create() + .show() + } + + binding.recyclerViewBackupFiles.adapter = adapter + binding.recyclerViewBackupFiles.layoutManager = LinearLayoutManager(context) + + model = ViewModelProvider(this).get(BackupFileViewModel::class.java) + model.backupFiles.observe(viewLifecycleOwner, Observer { files -> + files.let { adapter.setFiles(it) } + }) + + val swipeToDelete = object : SwipeToDelete(context!!) { + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + val file = adapter.getFileAndRemoveAt(viewHolder.adapterPosition) + val result = Shell.su( + "rm -f $backupFolder/$file" + ).exec() + if (result.isSuccess) Toast.makeText(context, getString(R.string.backup_deleted), Toast.LENGTH_SHORT).show() + } + } + val itemTouchHelper = ItemTouchHelper(swipeToDelete) + itemTouchHelper.attachToRecyclerView(binding.recyclerViewBackupFiles) + } + + private fun getBackupContents(file: String): MutableList { + return Shell.su( + ".$busyBox tar t -f $backupFolder/$file" + ).exec().out + } + + private fun getBackupFiles(): MutableList { + return Shell.su( + "ls $backupFolder" + ).exec().out + } + + @SuppressLint("SdCardPath") + private fun createBackup() { + var date = Calendar.getInstance().time.toString() + date = date.replace("\\s".toRegex(), "-") + val result = Shell.su( + "mkdir -p $backupFolder", + ".$busyBox tar c -zv -f $backupFolder/$date.tar.gz -C $overlayPath ." + ).exec() + Log.d("compress", result.out.toString()) + if (result.isSuccess) { + Toast.makeText(context, getString(R.string.backup_created), Toast.LENGTH_SHORT).show() + model.backupFiles.value = getBackupFiles() + } + } + + private fun restore() { + val intent = Intent(Intent.ACTION_GET_CONTENT) + intent.type = "application/gzip" + startActivityForResult(intent, 3) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + if (requestCode == 3 && resultCode == RESULT_OK && data != null) { + val selectedUri = data.data + val parcelFileDescriptor = selectedUri?.let { context!!.contentResolver.openFileDescriptor(it, "r", null) } + val inputStream = FileInputStream(parcelFileDescriptor?.fileDescriptor) + val backupFile = File(context!!.cacheDir, "acc.tar.gz") + inputStream.use { stream -> + backupFile.outputStream().use { + stream.copyTo(it) + } + } + + val result = Shell.su("[ -d $modPath ]").exec() + if (!result.isSuccess) { + Shell.su("mkdir -p $overlayPath").exec() + Shell.su(context!!.resources.openRawResource(R.raw.create_module)).exec() + } + + val restoreResult = Shell.su( + ".$busyBox tar x -zv -f $backupFile -C $overlayPath" + ).exec() + Log.d("restore", restoreResult.out.toString()) + if (restoreResult.isSuccess) { + context!!.cacheDir.delete() + showSnackbar(this.view!!, getString(R.string.accents_restored)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/akilesh/qacc/ui/fragments/HomeFragment.kt b/app/src/main/java/app/akilesh/qacc/ui/fragments/HomeFragment.kt index 0e250dd..c52829a 100644 --- a/app/src/main/java/app/akilesh/qacc/ui/fragments/HomeFragment.kt +++ b/app/src/main/java/app/akilesh/qacc/ui/fragments/HomeFragment.kt @@ -13,13 +13,13 @@ import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import app.akilesh.qacc.Const.Module.overlayPath +import app.akilesh.qacc.Const.Paths.overlayPath import app.akilesh.qacc.Const.prefix import app.akilesh.qacc.R import app.akilesh.qacc.ui.adapter.AccentListAdapter import app.akilesh.qacc.databinding.HomeFragmentBinding import app.akilesh.qacc.utils.AppUtils.showSnackbar -import app.akilesh.qacc.utils.SwipeToDeleteCallback +import app.akilesh.qacc.utils.SwipeToDelete import app.akilesh.qacc.viewmodel.AccentViewModel import com.topjohnwu.superuser.Shell @@ -53,7 +53,7 @@ class HomeFragment: Fragment() { accents?.let { adapter.setAccents(it) } }) - val swipeHandler = object : SwipeToDeleteCallback(context!!) { + val swipeHandler = object : SwipeToDelete(context!!) { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val accent = adapter.getAccentAndRemoveAt(viewHolder.adapterPosition) accentViewModel.delete(accent) diff --git a/app/src/main/java/app/akilesh/qacc/ui/fragments/SettingsFragment.kt b/app/src/main/java/app/akilesh/qacc/ui/fragments/SettingsFragment.kt index f3326a3..6f23dfb 100644 --- a/app/src/main/java/app/akilesh/qacc/ui/fragments/SettingsFragment.kt +++ b/app/src/main/java/app/akilesh/qacc/ui/fragments/SettingsFragment.kt @@ -1,8 +1,10 @@ package app.akilesh.qacc.ui.fragments import android.os.Build.VERSION.SDK_INT +import android.os.Build.VERSION_CODES.P import android.os.Build.VERSION_CODES.Q import android.os.Bundle +import androidx.navigation.fragment.findNavController import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat @@ -10,6 +12,7 @@ import androidx.preference.SwitchPreferenceCompat import app.akilesh.qacc.Const.isOOS import app.akilesh.qacc.R import app.akilesh.qacc.utils.AppUtils +import app.akilesh.qacc.utils.AppUtils.navAnim import com.github.javiersantos.appupdater.AppUpdater import com.github.javiersantos.appupdater.enums.UpdateFrom.JSON @@ -37,6 +40,13 @@ class SettingsFragment: PreferenceFragmentCompat() { true } + + val backupPref = findPreference("backups")!! + if (SDK_INT < P) backupPref.isVisible = false + backupPref.setOnPreferenceClickListener { + findNavController().navigate(R.id.backup_fragment, null, navAnim) + true + } } } diff --git a/app/src/main/java/app/akilesh/qacc/utils/AppUtils.kt b/app/src/main/java/app/akilesh/qacc/utils/AppUtils.kt index 98b6e00..c5a74b3 100644 --- a/app/src/main/java/app/akilesh/qacc/utils/AppUtils.kt +++ b/app/src/main/java/app/akilesh/qacc/utils/AppUtils.kt @@ -12,7 +12,8 @@ import androidx.annotation.AttrRes import androidx.annotation.ColorInt import androidx.appcompat.app.AppCompatDelegate import androidx.core.graphics.ColorUtils -import app.akilesh.qacc.Const.Module.overlayPath +import androidx.navigation.navOptions +import app.akilesh.qacc.Const.Paths.overlayPath import app.akilesh.qacc.Const.prefix import app.akilesh.qacc.R import app.akilesh.qacc.databinding.ColorPickerFragmentBinding @@ -104,6 +105,15 @@ object AppUtils { .show() } + val navAnim = navOptions { + anim { + enter = R.anim.fragment_enter + exit = R.anim.fragment_exit + popEnter = R.anim.fragment_enter_pop + popExit = R.anim.fragment_exit_pop + } + } + fun setPreview(binding: ColorPickerFragmentBinding, accentColor: Int) { diff --git a/app/src/main/java/app/akilesh/qacc/utils/SwipeToDeleteCallback.kt b/app/src/main/java/app/akilesh/qacc/utils/SwipeToDelete.kt similarity index 98% rename from app/src/main/java/app/akilesh/qacc/utils/SwipeToDeleteCallback.kt rename to app/src/main/java/app/akilesh/qacc/utils/SwipeToDelete.kt index 8ef25fd..221d9b5 100644 --- a/app/src/main/java/app/akilesh/qacc/utils/SwipeToDeleteCallback.kt +++ b/app/src/main/java/app/akilesh/qacc/utils/SwipeToDelete.kt @@ -12,7 +12,7 @@ import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import app.akilesh.qacc.R -abstract class SwipeToDeleteCallback(context: Context) : +abstract class SwipeToDelete(context: Context) : ItemTouchHelper.Callback() { private val clearPaint: Paint = Paint() private val background: ColorDrawable = ColorDrawable() diff --git a/app/src/main/java/app/akilesh/qacc/viewmodel/BackupFileViewModel.kt b/app/src/main/java/app/akilesh/qacc/viewmodel/BackupFileViewModel.kt new file mode 100644 index 0000000..34d1a00 --- /dev/null +++ b/app/src/main/java/app/akilesh/qacc/viewmodel/BackupFileViewModel.kt @@ -0,0 +1,11 @@ +package app.akilesh.qacc.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class BackupFileViewModel: ViewModel() { + + val backupFiles: MutableLiveData> by lazy { + MutableLiveData>() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_outline_backup.xml b/app/src/main/res/drawable/ic_outline_backup.xml new file mode 100644 index 0000000..65a8f16 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_backup.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_pageview.xml b/app/src/main/res/drawable/ic_outline_pageview.xml new file mode 100644 index 0000000..c1a1471 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_pageview.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_settings_backup_restore.xml b/app/src/main/res/drawable/ic_round_settings_backup_restore.xml new file mode 100644 index 0000000..02d3710 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_settings_backup_restore.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/backup_restore_fragment.xml b/app/src/main/res/layout/backup_restore_fragment.xml new file mode 100644 index 0000000..4f5a778 --- /dev/null +++ b/app/src/main/res/layout/backup_restore_fragment.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recyclerview_item_backups.xml b/app/src/main/res/layout/recyclerview_item_backups.xml new file mode 100644 index 0000000..8162165 --- /dev/null +++ b/app/src/main/res/layout/recyclerview_item_backups.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 21b3cb4..d0ce187 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -202,4 +202,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index 8a75d40..1c4b5d9 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -100,6 +100,13 @@ Magisk nicht gefunden! Beenden Erstellen fehlgeschlagen - + Backup and restore + Save your accents and restore them when needed + Restore + Backups in storage + Hex code + Backup file deleted + Backup created! + Accents restored! diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index d508937..7e5d35d 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -94,5 +94,13 @@ 未检测到Magisk! 退出 无法创建叠加层 + Backup and restore + Save your accents and restore them when needed + Restore + Backups in storage + Hex code + Backup file deleted + Backup created! + Accents restored! diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0a1629b..40ed8d8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -89,6 +89,15 @@ Magisk not detected! Exit Couldn\'t create overlay + Backup and restore + Save your accents and restore them when needed + Restore + Backups in storage + Backup contents + Hex code + Backup file deleted + Backup created! + Accents restored! diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index f49adae..41214d0 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -45,6 +45,12 @@ app:defaultValue="false" app:summary="@string/tweak_summary" /> + +