diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupFragment.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupFragment.kt index 2415f812..3b887056 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupFragment.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupFragment.kt @@ -1,10 +1,12 @@ package code.name.monkey.retromusic.fragments.backup +import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import android.view.MenuItem import android.view.View import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -16,9 +18,11 @@ import code.name.monkey.retromusic.R import code.name.monkey.retromusic.adapter.backup.BackupAdapter import code.name.monkey.retromusic.databinding.FragmentBackupBinding import code.name.monkey.retromusic.helper.BackupHelper +import code.name.monkey.retromusic.helper.sanitize import code.name.monkey.retromusic.util.BackupUtil import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.input.input +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.File @@ -42,25 +46,16 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC backupAdapter?.swapDataset(listOf()) } backupViewModel.loadBackups() - setupButtons() - } - - private fun setupButtons() { - binding.createBackup.setOnClickListener { - MaterialDialog(requireContext()).show { - title(res = R.string.action_rename) - input(prefill = System.currentTimeMillis().toString()) { _, text -> - // Text submitted with the action button - lifecycleScope.launch { - BackupHelper.createBackup(requireContext(), text.toString()) - backupViewModel.loadBackups() - } - } - positiveButton(android.R.string.ok) - negativeButton(R.string.action_cancel) - setTitle(R.string.title_new_backup) + val openFilePicker = registerForActivityResult(ActivityResultContracts.OpenDocument()) { + lifecycleScope.launch(Dispatchers.IO) { + backupViewModel.restoreBackup(requireActivity(), requireContext().contentResolver.openInputStream(it)) } - + } + binding.createBackup.setOnClickListener { + showCreateBackupDialog() + } + binding.restoreBackup.setOnClickListener { + openFilePicker.launch(arrayOf("application/octet-stream")) } } @@ -76,7 +71,7 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC private fun checkIsEmpty() { val isEmpty = backupAdapter!!.itemCount == 0 - binding.empty.isVisible = isEmpty + binding.emptyText.isVisible = isEmpty binding.backupTitle.isVisible = !isEmpty binding.backupRecyclerview.isVisible = !isEmpty } @@ -88,13 +83,31 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC } } + @SuppressLint("CheckResult") + private fun showCreateBackupDialog() { + MaterialDialog(requireContext()).show { + cornerRadius(res = R.dimen.m3_card_corner_radius) + title(res = R.string.action_rename) + input(prefill = System.currentTimeMillis().toString()) { _, text -> + // Text submitted with the action button + lifecycleScope.launch { + BackupHelper.createBackup(requireContext(), text.sanitize()) + backupViewModel.loadBackups() + } + } + positiveButton(android.R.string.ok) + negativeButton(R.string.action_cancel) + setTitle(R.string.title_new_backup) + } + } + override fun onBackupClicked(file: File) { AlertDialog.Builder(requireContext()) .setTitle(R.string.restore) .setMessage(R.string.restore_message) .setPositiveButton(R.string.restore) { _, _ -> lifecycleScope.launch { - backupViewModel.restoreBackup(requireActivity(), file) + backupViewModel.restoreBackup(requireActivity(), file.inputStream()) } } .setNegativeButton(android.R.string.cancel, null) @@ -102,6 +115,7 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC .show() } + @SuppressLint("CheckResult") override fun onBackupMenuClicked(file: File, menuItem: MenuItem): Boolean { when (menuItem.itemId) { R.id.action_delete -> { @@ -119,7 +133,11 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC } R.id.action_share -> { activity?.startActivity( - Intent.createChooser(BackupUtil.createShareFileIntent(file, requireContext()), null)) + Intent.createChooser( + BackupUtil.createShareFileIntent(file, requireContext()), + null + ) + ) return true } R.id.action_rename -> { diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupViewModel.kt index d2194749..920e70c9 100644 --- a/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/fragments/backup/BackupViewModel.kt @@ -9,6 +9,7 @@ import code.name.monkey.retromusic.helper.BackupHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File +import java.io.InputStream import kotlin.system.exitProcess @@ -24,8 +25,8 @@ class BackupViewModel : ViewModel() { } } - suspend fun restoreBackup(activity: Activity, file: File) { - BackupHelper.restoreBackup(activity, file) + suspend fun restoreBackup(activity: Activity, inputStream: InputStream?) { + BackupHelper.restoreBackup(activity, inputStream) withContext(Dispatchers.Main) { val intent = Intent( activity, diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt index 02eff857..e72dd9f1 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt @@ -15,7 +15,7 @@ import java.util.zip.ZipOutputStream object BackupHelper { suspend fun createBackup(context: Context, name: String) { val backupFile = - File(backupRootPath + name + APPEND_EXTENSION) + File(backupRootPath + File.separator + name + APPEND_EXTENSION) if (backupFile.parentFile?.exists() != true) { backupFile.parentFile?.mkdirs() } @@ -28,7 +28,7 @@ object BackupHelper { } } - private fun zipAll(zipItems: List, backupFile: File) { + private suspend fun zipAll(zipItems: List, backupFile: File) { try { ZipOutputStream(BufferedOutputStream(FileOutputStream(backupFile))).use { out -> for (zipItem in zipItems) { @@ -36,13 +36,17 @@ object BackupHelper { BufferedInputStream(fi).use { origin -> val entry = ZipEntry(zipItem.zipPath) out.putNextEntry(entry) - origin.copyTo(out, 1024) + origin.copyTo(out) } } } } } catch (exception: FileNotFoundException) { - Toast.makeText(App.getContext(), "Couldn't create backup", Toast.LENGTH_SHORT).show() + exception.printStackTrace() + withContext(Dispatchers.Main) { + Toast.makeText(App.getContext(), "Couldn't create backup", Toast.LENGTH_SHORT) + .show() + } } } @@ -72,9 +76,9 @@ object BackupHelper { } } - suspend fun restoreBackup(context: Context, file: File) { + suspend fun restoreBackup(context: Context, inputStream: InputStream?) { withContext(Dispatchers.IO) { - ZipInputStream(FileInputStream(file)).use { + ZipInputStream(inputStream).use { var entry = it.nextEntry while (entry != null) { if (entry.isDatabaseEntry()) restoreDatabase(context, it, entry) @@ -156,4 +160,17 @@ object BackupHelper { } } -data class ZipItem(val filePath: String, val zipPath: String) \ No newline at end of file +data class ZipItem(val filePath: String, val zipPath: String) + +fun CharSequence.sanitize(): String { + return toString().replace("/", "_") + .replace(":", "_") + .replace("*", "_") + .replace("?", "_") + .replace("\"", "_") + .replace("<", "_") + .replace(">", "_") + .replace("|", "_") + .replace("\\", "_") + .replace("&", "_") +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_backup.xml b/app/src/main/res/layout/fragment_backup.xml index c24a3399..9e1a3c9f 100644 --- a/app/src/main/res/layout/fragment_backup.xml +++ b/app/src/main/res/layout/fragment_backup.xml @@ -3,8 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> + android:layout_height="match_parent"> - - - - - - + app:layout_constraintTop_toTopOf="parent" + tools:visibility="visible" /> \ No newline at end of file