Fixed crash and added more options to backup
This commit is contained in:
parent
75410bf77b
commit
db3a7d4097
7 changed files with 171 additions and 26 deletions
|
@ -132,9 +132,6 @@
|
|||
<data android:pathPattern=".*\\.rmbak" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="code.name.monkey.retromusic.fragments.backup.BackupFragment"
|
||||
android:exported="true" />
|
||||
|
||||
<activity
|
||||
android:name=".appshortcuts.AppShortcutLauncherActivity"
|
||||
|
@ -169,7 +166,6 @@
|
|||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}"
|
||||
|
|
|
@ -1,23 +1,27 @@
|
|||
package code.name.monkey.retromusic.adapter.backup
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.PopupMenu
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import code.name.monkey.retromusic.R
|
||||
import java.io.File
|
||||
|
||||
|
||||
class BackupAdapter(
|
||||
val context: Context,
|
||||
val activity: FragmentActivity,
|
||||
var dataSet: MutableList<File>,
|
||||
val backupClickedListener: BackupClickedListener
|
||||
) : RecyclerView.Adapter<BackupAdapter.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
return ViewHolder(
|
||||
LayoutInflater.from(context).inflate(R.layout.item_list_card, parent, false)
|
||||
LayoutInflater.from(activity).inflate(R.layout.item_list_card, parent, false)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -34,8 +38,20 @@ class BackupAdapter(
|
|||
|
||||
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val title: TextView = itemView.findViewById(R.id.title)
|
||||
val menu: AppCompatImageView = itemView.findViewById(R.id.menu)
|
||||
|
||||
init {
|
||||
menu.setOnClickListener { view ->
|
||||
val popupMenu = PopupMenu(activity, view)
|
||||
popupMenu.inflate(R.menu.menu_backup)
|
||||
popupMenu.setOnMenuItemClickListener { menuItem ->
|
||||
return@setOnMenuItemClickListener backupClickedListener.onBackupMenuClicked(
|
||||
dataSet[bindingAdapterPosition],
|
||||
menuItem
|
||||
)
|
||||
}
|
||||
popupMenu.show()
|
||||
}
|
||||
itemView.setOnClickListener {
|
||||
backupClickedListener.onBackupClicked(dataSet[bindingAdapterPosition])
|
||||
}
|
||||
|
@ -44,5 +60,7 @@ class BackupAdapter(
|
|||
|
||||
interface BackupClickedListener {
|
||||
fun onBackupClicked(file: File)
|
||||
|
||||
fun onBackupMenuClicked(file: File, menuItem: MenuItem): Boolean
|
||||
}
|
||||
}
|
|
@ -1,7 +1,11 @@
|
|||
package code.name.monkey.retromusic.fragments.backup
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
|
@ -12,6 +16,9 @@ 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.util.BackupUtil
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.input.input
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
|
@ -40,15 +47,25 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC
|
|||
|
||||
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())
|
||||
BackupHelper.createBackup(requireContext(), text.toString())
|
||||
backupViewModel.loadBackups()
|
||||
}
|
||||
}
|
||||
positiveButton(R.string.action_rename)
|
||||
negativeButton(R.string.action_cancel)
|
||||
setTitle(R.string.action_rename)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun initAdapter() {
|
||||
backupAdapter = BackupAdapter(requireContext(), ArrayList(), this)
|
||||
backupAdapter = BackupAdapter(requireActivity(), ArrayList(), this)
|
||||
backupAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onChanged() {
|
||||
super.onChanged()
|
||||
|
@ -72,8 +89,64 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onBackupMenuClicked(file: File, menuItem: MenuItem): Boolean {
|
||||
when (menuItem.itemId) {
|
||||
R.id.action_delete -> {
|
||||
try {
|
||||
file.delete()
|
||||
} catch (exception: SecurityException) {
|
||||
Toast.makeText(
|
||||
activity,
|
||||
"Could not delete backup",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
backupViewModel.loadBackups()
|
||||
return true
|
||||
}
|
||||
R.id.action_share -> {
|
||||
activity?.startActivity(
|
||||
Intent.createChooser(BackupUtil.createShareFileIntent(file, requireContext()), null))
|
||||
return true
|
||||
}
|
||||
R.id.action_rename -> {
|
||||
MaterialDialog(requireContext()).show {
|
||||
title(res = R.string.action_rename)
|
||||
input(prefill = file.nameWithoutExtension) { _, text ->
|
||||
// Text submitted with the action button
|
||||
val renamedFile =
|
||||
File(file.parent + File.separator + text + BackupHelper.APPEND_EXTENSION)
|
||||
if (!renamedFile.exists()) {
|
||||
file.renameTo(renamedFile)
|
||||
backupViewModel.loadBackups()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
"File already exists",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
positiveButton(R.string.action_rename)
|
||||
negativeButton(R.string.action_cancel)
|
||||
setTitle(R.string.action_rename)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package code.name.monkey.retromusic.helper
|
|||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import code.name.monkey.retromusic.App
|
||||
import code.name.monkey.retromusic.BuildConfig
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -12,20 +13,24 @@ import java.util.zip.ZipInputStream
|
|||
import java.util.zip.ZipOutputStream
|
||||
|
||||
object BackupHelper {
|
||||
suspend fun createBackup(context: Context) {
|
||||
suspend fun createBackup(context: Context, name: String) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val finalPath =
|
||||
backupRootPath + System.currentTimeMillis().toString() + APPEND_EXTENSION
|
||||
val backupFile =
|
||||
File(backupRootPath + name + APPEND_EXTENSION)
|
||||
if (backupFile.parentFile?.exists() != true) {
|
||||
backupFile.parentFile?.mkdirs()
|
||||
}
|
||||
val zipItems = mutableListOf<ZipItem>()
|
||||
zipItems.addAll(getDatabaseZipItems(context))
|
||||
zipItems.addAll(getSettingsZipItems(context))
|
||||
getUserImageZipItems(context)?.let { zipItems.addAll(it) }
|
||||
zipAll(zipItems, finalPath)
|
||||
zipAll(zipItems, backupFile)
|
||||
}
|
||||
}
|
||||
|
||||
private fun zipAll(zipItems: List<ZipItem>, finalPath: String) {
|
||||
ZipOutputStream(BufferedOutputStream(FileOutputStream(finalPath))).use { out ->
|
||||
private fun zipAll(zipItems: List<ZipItem>, backupFile: File) {
|
||||
try {
|
||||
ZipOutputStream(BufferedOutputStream(FileOutputStream(backupFile))).use { out ->
|
||||
for (zipItem in zipItems) {
|
||||
FileInputStream(zipItem.filePath).use { fi ->
|
||||
BufferedInputStream(fi).use { origin ->
|
||||
|
@ -36,6 +41,9 @@ object BackupHelper {
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch (exception: FileNotFoundException) {
|
||||
Toast.makeText(App.getContext(), "Couldn't create backup", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDatabaseZipItems(context: Context): List<ZipItem> {
|
||||
|
@ -124,7 +132,7 @@ object BackupHelper {
|
|||
val backupRootPath =
|
||||
Environment.getExternalStorageDirectory().toString() + "/RetroMusic/Backups/"
|
||||
const val BACKUP_EXTENSION = "rmbak"
|
||||
private const val APPEND_EXTENSION = ".$BACKUP_EXTENSION"
|
||||
const val APPEND_EXTENSION = ".$BACKUP_EXTENSION"
|
||||
private const val DATABASES_PATH = "databases"
|
||||
private const val SETTINGS_PATH = "prefs"
|
||||
private const val IMAGES_PATH = "userImages"
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
package code.name.monkey.retromusic.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.FileProvider
|
||||
import java.io.File
|
||||
|
||||
object BackupUtil {
|
||||
fun createShareFileIntent(file: File, context: Context): Intent? {
|
||||
return try {
|
||||
Intent().setAction(Intent.ACTION_SEND).putExtra(
|
||||
Intent.EXTRA_STREAM,
|
||||
FileProvider.getUriForFile(
|
||||
context,
|
||||
context.applicationContext.packageName,
|
||||
file
|
||||
)
|
||||
).addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION).setType("*/*")
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.printStackTrace()
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Could not share this file.",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
Intent()
|
||||
}
|
||||
}
|
||||
}
|
19
app/src/main/res/menu/menu_backup.xml
Normal file
19
app/src/main/res/menu/menu_backup.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_delete"
|
||||
android:icon="@drawable/ic_delete"
|
||||
android:title="@string/action_delete"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_share"
|
||||
android:icon="@drawable/ic_share"
|
||||
android:title="@string/action_share"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_rename"
|
||||
android:icon="@drawable/ic_edit"
|
||||
android:title="@string/action_rename"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
|
@ -514,4 +514,5 @@
|
|||
<string name="you_have_to_select_at_least_one_category">You have to select at least one category.</string>
|
||||
<string name="you_will_be_forwarded_to_the_issue_tracker_website">You will be forwarded to the issue tracker website.</string>
|
||||
<string name="your_account_data_is_only_used_for_authentication">Your account data is only used for authentication.</string>
|
||||
<string name="restore_message">Do you want to restore backup?</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue