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