diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 20108f3d1..71977a668 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -123,15 +123,38 @@
+ android:excludeFromRecents="false"
+ android:exported="true"
+ android:label="@string/restore"
+ android:theme="@style/Theme.RetroMusic.Dialog">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -273,10 +296,9 @@
+ android:label="@string/app_name">
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 ab29cd830..170ae9387 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
@@ -7,7 +7,7 @@ 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.net.toUri
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -49,7 +49,9 @@ class BackupFragment : Fragment(R.layout.fragment_backup), BackupAdapter.BackupC
val openFilePicker = registerForActivityResult(ActivityResultContracts.OpenDocument()) {
lifecycleScope.launch(Dispatchers.IO) {
it?.let {
- backupViewModel.restoreBackup(requireActivity(), requireContext().contentResolver.openInputStream(it))
+ startActivity(Intent(context, RestoreActivity::class.java).apply {
+ data = it
+ })
}
}
}
@@ -103,17 +105,11 @@ 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.inputStream())
- }
- }
- .setNegativeButton(android.R.string.cancel, null)
- .create()
- .show()
+ lifecycleScope.launch {
+ startActivity(Intent(context, RestoreActivity::class.java).apply {
+ data = file.toUri()
+ })
+ }
}
@SuppressLint("CheckResult")
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 920e70c91..f482cc915 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
@@ -5,6 +5,8 @@ import android.content.Intent
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
+import code.name.monkey.retromusic.activities.MainActivity
+import code.name.monkey.retromusic.helper.BackupContent
import code.name.monkey.retromusic.helper.BackupHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -25,12 +27,12 @@ class BackupViewModel : ViewModel() {
}
}
- suspend fun restoreBackup(activity: Activity, inputStream: InputStream?) {
- BackupHelper.restoreBackup(activity, inputStream)
+ suspend fun restoreBackup(activity: Activity, inputStream: InputStream?, contents: List) {
+ BackupHelper.restoreBackup(activity, inputStream, contents)
withContext(Dispatchers.Main) {
val intent = Intent(
activity,
- activity::class.java
+ MainActivity::class.java
)
activity.startActivity(intent)
exitProcess(0)
diff --git a/app/src/main/java/code/name/monkey/retromusic/fragments/backup/RestoreActivity.kt b/app/src/main/java/code/name/monkey/retromusic/fragments/backup/RestoreActivity.kt
index f54e697c2..feab240ea 100644
--- a/app/src/main/java/code/name/monkey/retromusic/fragments/backup/RestoreActivity.kt
+++ b/app/src/main/java/code/name/monkey/retromusic/fragments/backup/RestoreActivity.kt
@@ -1,12 +1,89 @@
package code.name.monkey.retromusic.fragments.backup
+import android.net.Uri
import android.os.Bundle
+import android.provider.MediaStore
+import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
-import code.name.monkey.retromusic.R
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.lifecycle.lifecycleScope
+import code.name.monkey.retromusic.databinding.ActivityRestoreBinding
+import code.name.monkey.retromusic.helper.BackupContent
+import code.name.monkey.retromusic.helper.BackupContent.*
+import code.name.monkey.retromusic.util.PreferenceUtil
+import code.name.monkey.retromusic.util.theme.ThemeManager
+import com.google.android.material.color.DynamicColors
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
class RestoreActivity : AppCompatActivity() {
+
+ lateinit var binding: ActivityRestoreBinding
+ private val backupViewModel: BackupViewModel by viewModels()
+
override fun onCreate(savedInstanceState: Bundle?) {
+ updateTheme()
super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_restore)
+ binding = ActivityRestoreBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ val backupUri = intent?.data
+ binding.backupName.setText(getFileName(backupUri))
+ binding.cancelButton.setOnClickListener {
+ finish()
+ }
+ binding.restoreButton.setOnClickListener {
+ val backupContents = mutableListOf()
+ if (binding.checkSettings.isChecked) backupContents.add(SETTINGS)
+ if (binding.checkQueue.isChecked) backupContents.add(QUEUE)
+ if (binding.checkDatabases.isChecked) backupContents.add(PLAYLISTS)
+ if (binding.checkArtistImages.isChecked) backupContents.add(CUSTOM_ARTIST_IMAGES)
+ if (binding.checkUserImages.isChecked) backupContents.add(USER_IMAGES)
+ lifecycleScope.launch(Dispatchers.IO) {
+ if (backupUri != null) {
+ contentResolver.openInputStream(backupUri)?.use {
+ backupViewModel.restoreBackup(this@RestoreActivity, it, backupContents)
+ }
+ }
+ }
+ }
+ }
+
+ private fun updateTheme() {
+ AppCompatDelegate.setDefaultNightMode(ThemeManager.getNightMode(this))
+
+ // Apply dynamic colors to activity if enabled
+ if (PreferenceUtil.materialYou) {
+ DynamicColors.applyIfAvailable(
+ this,
+ com.google.android.material.R.style.ThemeOverlay_Material3_DynamicColors_DayNight
+ )
+ }
+ }
+
+ private fun getFileName(uri: Uri?): String? {
+ when (uri?.scheme) {
+ "file" -> {
+ return uri.lastPathSegment
+ }
+ "content" -> {
+ val proj = arrayOf(MediaStore.Images.Media.TITLE)
+ contentResolver.query(
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
+ MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
+ } else {
+ MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
+ }, proj, null, null, null
+ )?.use { cursor ->
+ if (cursor.count != 0) {
+ val columnIndex: Int =
+ cursor.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE)
+ cursor.moveToFirst()
+ return cursor.getString(columnIndex)
+ }
+ }
+ }
+ }
+ return "Backup"
}
}
\ No newline at end of file
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 aefe5511a..3b1e1cde2 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
@@ -2,9 +2,13 @@ package code.name.monkey.retromusic.helper
import android.content.Context
import android.os.Environment
+import android.util.Log
import android.widget.Toast
+import androidx.core.content.edit
+import androidx.preference.PreferenceManager
import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.BuildConfig
+import code.name.monkey.retromusic.helper.BackupContent.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.*
@@ -24,10 +28,11 @@ object BackupHelper {
zipItems.addAll(getSettingsZipItems(context))
getUserImageZipItems(context)?.let { zipItems.addAll(it) }
zipItems.addAll(getCustomArtistZipItems(context))
+ zipItems.addAll(getQueueZipItems(context))
zipAll(zipItems, backupFile)
}
- private suspend fun zipAll(zipItems: List, backupFile: File) {
+ private suspend fun zipAll(zipItems: List, backupFile: File) =
withContext(Dispatchers.IO) {
kotlin.runCatching {
ZipOutputStream(BufferedOutputStream(FileOutputStream(backupFile))).use { out ->
@@ -42,27 +47,42 @@ object BackupHelper {
}
}
}.onFailure {
- it.printStackTrace()
withContext(Dispatchers.Main) {
Toast.makeText(App.getContext(), "Couldn't create backup", Toast.LENGTH_SHORT)
.show()
}
+ throw Exception(it)
+ }.onSuccess {
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ App.getContext(),
+ "Backup created successfully",
+ Toast.LENGTH_SHORT
+ )
+ .show()
+ }
}
- withContext(Dispatchers.Main) {
- Toast.makeText(App.getContext(), "Backup created successfully", Toast.LENGTH_SHORT)
- .show()
- }
+
}
- }
private fun getDatabaseZipItems(context: Context): List {
return context.databaseList().filter {
- it.endsWith(".db")
+ it.endsWith(".db") && it != queueDatabase
}.map {
ZipItem(context.getDatabasePath(it).absolutePath, "$DATABASES_PATH${File.separator}$it")
}
}
+ private fun getQueueZipItems(context: Context): List {
+ Log.d("RetroMusic", context.getDatabasePath(queueDatabase).absolutePath)
+ return listOf(
+ ZipItem(
+ context.getDatabasePath(queueDatabase).absolutePath,
+ "$QUEUE_PATH${File.separator}$queueDatabase"
+ )
+ )
+ }
+
private fun getSettingsZipItems(context: Context): List {
val sharedPrefPath = context.filesDir.parentFile?.absolutePath + "/shared_prefs/"
return listOf(
@@ -94,33 +114,47 @@ object BackupHelper {
)
}?.toList() ?: listOf()
)
- zipItemList.add(
- ZipItem(
- sharedPrefPath + File.separator + "custom_artist_image.xml",
- "$CUSTOM_ARTISTS_PATH${File.separator}prefs${File.separator}custom_artist_image.xml"
- )
- )
+ File(sharedPrefPath + File.separator + "custom_artist_image.xml").let {
+ if (it.exists()) {
+ zipItemList.add(
+ ZipItem(
+ it.absolutePath,
+ "$CUSTOM_ARTISTS_PATH${File.separator}prefs${File.separator}custom_artist_image.xml"
+ )
+ )
+ }
+ }
+
+
return zipItemList
}
- suspend fun restoreBackup(context: Context, inputStream: InputStream?) {
+ suspend fun restoreBackup(
+ context: Context,
+ inputStream: InputStream?,
+ contents: List
+ ) {
withContext(Dispatchers.IO) {
ZipInputStream(inputStream).use {
var entry = it.nextEntry
while (entry != null) {
- if (entry.isDatabaseEntry()) restoreDatabase(context, it, entry)
- if (entry.isPreferenceEntry()) restorePreferences(context, it, entry)
- if (entry.isImageEntry()) restoreImages(context, it, entry)
- if (entry.isCustomArtistImageEntry()) restoreCustomArtistImages(
- context,
- it,
- entry
- )
- if (entry.isCustomArtistPrefEntry()) restoreCustomArtistPrefs(
- context,
- it,
- entry
- )
+ if (entry.isDatabaseEntry() && contents.contains(PLAYLISTS)) {
+ restoreDatabase(context, it, entry)
+ } else if (entry.isPreferenceEntry() && contents.contains(SETTINGS)) {
+ restorePreferences(context, it, entry)
+ } else if (entry.isImageEntry() && contents.contains(USER_IMAGES)) {
+ restoreImages(context, it, entry)
+
+ } else if (entry.isCustomArtistImageEntry() && contents.contains(
+ CUSTOM_ARTIST_IMAGES
+ )
+ ) {
+ restoreCustomArtistImages(context, it, entry)
+ restoreCustomArtistPrefs(context, it, entry)
+ } else if (entry.isQueueEntry() && contents.contains(QUEUE)) {
+ restoreQueueDatabase(context, it, entry)
+ }
+
entry = it.nextEntry
}
}
@@ -170,6 +204,21 @@ object BackupHelper {
}
}
+ private fun restoreQueueDatabase(context: Context, zipIn: ZipInputStream, zipEntry: ZipEntry) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit(commit = true) {
+ putInt("POSITION", 0)
+ }
+ val filePath =
+ context.filesDir.parent!! + File.separator + DATABASES_PATH + File.separator + zipEntry.getFileName()
+ BufferedOutputStream(FileOutputStream(filePath)).use { bos ->
+ val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE)
+ var read: Int
+ while (zipIn.read(bytesIn).also { read = it } != -1) {
+ bos.write(bytesIn, 0, read)
+ }
+ }
+ }
+
private fun restoreCustomArtistImages(
context: Context,
zipIn: ZipInputStream,
@@ -218,10 +267,12 @@ object BackupHelper {
const val BACKUP_EXTENSION = "rmbak"
const val APPEND_EXTENSION = ".$BACKUP_EXTENSION"
private const val DATABASES_PATH = "databases"
+ private const val QUEUE_PATH = "queue"
private const val SETTINGS_PATH = "prefs"
private const val IMAGES_PATH = "userImages"
private const val CUSTOM_ARTISTS_PATH = "artistImages"
private const val THEME_PREFS_KEY_DEFAULT = "[[kabouzeid_app-theme-helper]]"
+ private const val queueDatabase = "music_playback_state.db"
private fun ZipEntry.isDatabaseEntry(): Boolean {
return name.startsWith(DATABASES_PATH)
@@ -243,6 +294,10 @@ object BackupHelper {
return name.startsWith(CUSTOM_ARTISTS_PATH) && name.contains("prefs")
}
+ private fun ZipEntry.isQueueEntry(): Boolean {
+ return name.startsWith(QUEUE_PATH)
+ }
+
private fun ZipEntry.getFileName(): String {
return name.substring(name.lastIndexOf(File.separator))
}
@@ -261,4 +316,12 @@ fun CharSequence.sanitize(): String {
.replace("|", "_")
.replace("\\", "_")
.replace("&", "_")
+}
+
+enum class BackupContent {
+ SETTINGS,
+ USER_IMAGES,
+ CUSTOM_ARTIST_IMAGES,
+ PLAYLISTS,
+ QUEUE
}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/rounded_drawable.xml b/app/src/main/res/drawable/rounded_drawable.xml
new file mode 100644
index 000000000..6d498dbf2
--- /dev/null
+++ b/app/src/main/res/drawable/rounded_drawable.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_restore.xml b/app/src/main/res/layout/activity_restore.xml
index 79eda3368..fb38360b5 100644
--- a/app/src/main/res/layout/activity_restore.xml
+++ b/app/src/main/res/layout/activity_restore.xml
@@ -1,8 +1,83 @@
-
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:padding="16dp">
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 4ba9ea8e8..862ba952f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -73,6 +73,7 @@
Full Image
Card
Classic
+ MD3
Small
Minimal Text
Artist
@@ -81,6 +82,7 @@
Audio focus denied.
Change the sound settings and adjust the equalizer controls
Auto
+ Backup and restore your settings, playlists
@@ -113,6 +115,7 @@
Cascading
Changelog
Check out What\'s New
+ Choose what to restore
Circle
Circular
Classic
@@ -132,7 +135,9 @@
Created playlist %1$s.
Members and contributors
Currently listening to %1$s by %2$s.
+ Custom Artist Images
Kinda Dark
+ Databases (Playlists, History, Most Played, etc.)
Delete playlist
%1$s?]]>
@@ -156,6 +161,7 @@
Device info
Allow Retro Music to modify audio settings
Set ringtone
+ Disc Number
Do you want to clear the blacklist?
%1$s from the blacklist?]]>
@@ -404,6 +410,7 @@
Reset
Reset artist image
Restore
+ Do you want to restore backup?
Restored previous purchase. Please restart the app to make use of all features.
Restored previous purchases.
Restoring purchase…
@@ -453,8 +460,8 @@
Sort order
Ascending
Album
- Artist
@string/album_artist
+ Artist
Composer
Date added
Date modified
@@ -480,6 +487,7 @@
Tiny
Tiny card
Title
+ New Backup
Today
Top albums
Top artists
@@ -495,6 +503,7 @@
Up next
Update image
Updating…
+ User Images
User Name
Username
Version
@@ -515,9 +524,4 @@
You have to select at least one category.
You will be forwarded to the issue tracker website.
Your account data is only used for authentication.
- Do you want to restore backup?
- New Backup
- Backup and restore your settings, playlists
- MD3
- Disc Number
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index cf607b8c6..e8e081dff 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -236,4 +236,13 @@
- @color/md_deep_purple_A400
- @color/md_deep_purple_500
+
+