Fixed crash and added more options to backup

This commit is contained in:
Prathamesh More 2021-10-23 21:31:33 +05:30
parent 75410bf77b
commit db3a7d4097
7 changed files with 171 additions and 26 deletions

View file

@ -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}"

View file

@ -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
}
}

View file

@ -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 {
lifecycleScope.launch {
BackupHelper.createBackup(requireContext())
backupViewModel.loadBackups()
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(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) {
lifecycleScope.launch {
backupViewModel.restoreBackup(requireActivity(), 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
}
}

View file

@ -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,29 +13,36 @@ 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 ->
for (zipItem in zipItems) {
FileInputStream(zipItem.filePath).use { fi ->
BufferedInputStream(fi).use { origin ->
val entry = ZipEntry(zipItem.zipPath)
out.putNextEntry(entry)
origin.copyTo(out, 1024)
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 ->
val entry = ZipEntry(zipItem.zipPath)
out.putNextEntry(entry)
origin.copyTo(out, 1024)
}
}
}
}
} catch (exception: FileNotFoundException) {
Toast.makeText(App.getContext(), "Couldn't create backup", Toast.LENGTH_SHORT).show()
}
}
@ -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"

View file

@ -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()
}
}
}

View 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>

View file

@ -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>