diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a5235207f..b264b02cf 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,7 +8,7 @@
-
+
@@ -119,6 +119,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ android:screenOrientation="portrait"
+ android:theme="@style/Theme.AppCompat.NoActionBar">
-
+
+ android:value=".activities.MainActivity" />
+ android:resource="@xml/automotive_app_desc" />
+ android:resource="@drawable/ic_notification" />
()
+ private var backupAdapter: BackupAdapter? = null
+
+ lateinit var binding: ActivityBackupBinding
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityBackupBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ binding.appBarLayout.statusBarForeground =
+ MaterialShapeDrawable.createWithElevationOverlay(this)
+ binding.toolbar.setNavigationIcon(R.drawable.ic_keyboard_backspace_black)
+ initAdapter()
+ setupRecyclerview()
+ backupViewModel.backupsLiveData.observe(this) {
+ if (it.isNotEmpty())
+ backupAdapter?.swapDataset(it)
+ else
+ backupAdapter?.swapDataset(listOf())
+ }
+ backupViewModel.loadBackups()
+ setupButtons()
+ }
+
+ private fun setupButtons() {
+ binding.createBackup.setOnClickListener {
+ lifecycleScope.launch {
+ BackupHelper.createBackup(this@BackupActivity)
+ backupViewModel.loadBackups()
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ this@BackupActivity,
+ "Backup Completed Successfully",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+ }
+
+ private fun initAdapter() {
+ backupAdapter = BackupAdapter(this@BackupActivity, ArrayList())
+ backupAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
+ override fun onChanged() {
+ super.onChanged()
+ checkIsEmpty()
+ }
+ })
+ }
+
+ private fun checkIsEmpty() {
+ binding.emptyText.setText(R.string.no_backups_found)
+ (backupAdapter!!.itemCount == 0).run {
+ binding.empty.isVisible = this
+ binding.backupTitle.isVisible = this
+ }
+ }
+
+ fun setupRecyclerview() {
+ binding.backupRecyclerview.apply {
+ layoutManager = LinearLayoutManager(context)
+ adapter = backupAdapter
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/backup/BackupViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/activities/backup/BackupViewModel.kt
new file mode 100644
index 000000000..05a51b25a
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/backup/BackupViewModel.kt
@@ -0,0 +1,23 @@
+package code.name.monkey.retromusic.activities.backup
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import code.name.monkey.retromusic.helper.BackupHelper
+import kotlinx.coroutines.launch
+import java.io.File
+
+class BackupViewModel : ViewModel() {
+ private val backupsMutableLiveData = MutableLiveData>()
+ val backupsLiveData = backupsMutableLiveData
+
+ fun loadBackups() {
+ viewModelScope.launch {
+ File(BackupHelper.backupRootPath).listFiles { _, name ->
+ return@listFiles name.endsWith(BackupHelper.backupExt)
+ }?.toList()?.let {
+ backupsMutableLiveData.value = it
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/backup/RestoreActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/backup/RestoreActivity.kt
new file mode 100644
index 000000000..6caabf7ff
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/activities/backup/RestoreActivity.kt
@@ -0,0 +1,12 @@
+package code.name.monkey.retromusic.activities.backup
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import code.name.monkey.retromusic.R
+
+class RestoreActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_restore)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt
new file mode 100644
index 000000000..276e95a92
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt
@@ -0,0 +1,40 @@
+package code.name.monkey.retromusic.adapter.backup
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import code.name.monkey.retromusic.R
+import java.io.File
+
+class BackupAdapter(
+ val context: Context,
+ var dataSet: MutableList,
+) : RecyclerView.Adapter() {
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ return ViewHolder(
+ LayoutInflater.from(context).inflate(R.layout.item_list_card, parent, false)
+ )
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.title.text = dataSet[position].nameWithoutExtension
+ }
+
+ override fun getItemCount(): Int = dataSet.size
+
+ fun swapDataset(dataSet: List) {
+ this.dataSet = ArrayList(dataSet)
+ notifyDataSetChanged()
+ }
+
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val title: TextView = itemView.findViewById(R.id.title)
+
+ init {
+ }
+ }
+}
\ 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
new file mode 100644
index 000000000..80589b57e
--- /dev/null
+++ b/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt
@@ -0,0 +1,71 @@
+package code.name.monkey.retromusic.helper
+
+import android.content.Context
+import android.os.Environment
+import code.name.monkey.retromusic.BuildConfig
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import java.io.*
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
+
+object BackupHelper {
+ suspend fun createBackup(context: Context) {
+ withContext(Dispatchers.IO) {
+ val finalPath = backupRootPath + System.currentTimeMillis().toString() + backupExt
+ val zipItems = mutableListOf()
+ zipItems.addAll(getDatabaseZipItems(context))
+ zipItems.addAll(getSettingsZipItems(context))
+ getUserImageZipItems(context)?.let { zipItems.addAll(it) }
+ zipAll(zipItems, finalPath)
+ }
+ }
+
+ private fun zipAll(zipItems: List, 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 getDatabaseZipItems(context: Context): List {
+ return context.databaseList().filter {
+ it.endsWith(".db")
+ }.map {
+ ZipItem(context.getDatabasePath(it).absolutePath, "databases${File.separator}$it")
+ }
+ }
+
+ private fun getSettingsZipItems(context: Context): List {
+ val sharedPrefPath = context.filesDir.parentFile?.absolutePath + "/shared_prefs/"
+ return listOf(
+ "${BuildConfig.APPLICATION_ID}_preferences.xml", // App settings pref path
+ "$THEME_PREFS_KEY_DEFAULT.xml" // appthemehelper pref path
+ ).map {
+ ZipItem(sharedPrefPath + it, "preferences${File.separator}$it")
+ }
+ }
+
+ private fun getUserImageZipItems(context: Context): List? {
+ return context.filesDir.listFiles { _, name ->
+ name.endsWith(".jpg")
+ }?.map {
+ ZipItem(it.absolutePath, "userImages${File.separator}${it.name}")
+ }
+ }
+
+ val backupRootPath =
+ Environment.getExternalStorageDirectory().toString() + "/RetroMusic/Backups/"
+ const val backupExt = ".rmbak"
+ private const val THEME_PREFS_KEY_DEFAULT = "[[kabouzeid_app-theme-helper]]"
+
+}
+
+data class ZipItem(val filePath: String, val zipPath: String)
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_backup.xml b/app/src/main/res/drawable/ic_backup.xml
new file mode 100644
index 000000000..9faa168d2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_backup.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_restore.xml b/app/src/main/res/drawable/ic_restore.xml
new file mode 100644
index 000000000..d4fa3ee61
--- /dev/null
+++ b/app/src/main/res/drawable/ic_restore.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_backup.xml b/app/src/main/res/layout/activity_backup.xml
new file mode 100644
index 000000000..1bd0ae067
--- /dev/null
+++ b/app/src/main/res/layout/activity_backup.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 000000000..273ec1089
--- /dev/null
+++ b/app/src/main/res/layout/activity_restore.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_list_card.xml b/app/src/main/res/layout/item_list_card.xml
new file mode 100644
index 000000000..25a71a9bc
--- /dev/null
+++ b/app/src/main/res/layout/item_list_card.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file