Feature: Added the ability to backup and restore apps.

This commit is contained in:
HeCodes2Much
2024-12-02 10:42:45 +00:00
parent 198b2308a5
commit b874d55569
6 changed files with 122 additions and 8 deletions

View File

@@ -19,6 +19,12 @@ interface AppInfoDAO {
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertAll(apps: List<AppInfo>) suspend fun insertAll(apps: List<AppInfo>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun restoreAll(apps: List<AppInfo>)
@Query("DELETE FROM app")
suspend fun clearAll()
@Update @Update
suspend fun update(app: AppInfo) suspend fun update(app: AppInfo)

View File

@@ -31,10 +31,14 @@ import com.github.droidworksstudio.common.showLongToast
import com.github.droidworksstudio.launcher.BuildConfig import com.github.droidworksstudio.launcher.BuildConfig
import com.github.droidworksstudio.launcher.R import com.github.droidworksstudio.launcher.R
import com.github.droidworksstudio.launcher.accessibility.ActionService import com.github.droidworksstudio.launcher.accessibility.ActionService
import com.github.droidworksstudio.launcher.data.dao.AppInfoDAO
import com.github.droidworksstudio.launcher.data.entities.AppInfo
import com.github.droidworksstudio.launcher.helper.weather.WeatherResponse import com.github.droidworksstudio.launcher.helper.weather.WeatherResponse
import com.github.droidworksstudio.launcher.utils.Constants import com.github.droidworksstudio.launcher.utils.Constants
import com.github.droidworksstudio.launcher.utils.WeatherApiService import com.github.droidworksstudio.launcher.utils.WeatherApiService
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.flow.first
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.net.UnknownHostException import java.net.UnknownHostException
@@ -316,7 +320,7 @@ class AppHelper @Inject constructor() {
fun storeFile(activity: Activity) { fun storeFile(activity: Activity) {
// Generate a unique filename with a timestamp // Generate a unique filename with a timestamp
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val fileName = "backup_$timeStamp.json" val fileName = "Settings_backup_$timeStamp.json"
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
@@ -334,6 +338,67 @@ class AppHelper @Inject constructor() {
activity.startActivityForResult(intent, Constants.BACKUP_READ, null) activity.startActivityForResult(intent, Constants.BACKUP_READ, null)
} }
fun storeFileApps(activity: Activity) {
// Generate a unique filename with a timestamp
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val fileName = "Apps_backup_$timeStamp.json"
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/json"
putExtra(Intent.EXTRA_TITLE, fileName)
}
activity.startActivityForResult(intent, Constants.BACKUP_WRITE_APPS, null)
}
fun loadFileApps(activity: Activity) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/json"
}
activity.startActivityForResult(intent, Constants.BACKUP_READ_APPS, null)
}
suspend fun backupAppInfo(context: Context, dao: AppInfoDAO, uri: Uri) {
try {
dao.getAllAppsFlow()
.first() // Get the first emission from the Flow
.let { allApps ->
val gson = Gson()
val jsonString = gson.toJson(allApps)
// Write JSON to the selected URI
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
outputStream.write(jsonString.toByteArray())
} ?: throw Exception("Failed to open output stream")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
suspend fun restoreAppInfo(context: Context, dao: AppInfoDAO, uri: Uri) {
try {
// Open an InputStream from the selected Uri
context.contentResolver.openInputStream(uri)?.use { inputStream ->
// Read the content from the InputStream
val jsonString = inputStream.bufferedReader().use { it.readText() }
// Convert JSON to List<AppInfo>
val gson = Gson()
val type = object : TypeToken<List<AppInfo>>() {}.type
val appInfoList: List<AppInfo> = gson.fromJson(jsonString, type)
// Reinsert data into the database
dao.restoreAll(appInfoList)
} ?: throw Exception("Failed to open input stream from URI")
} catch (e: Exception) {
e.printStackTrace()
}
}
fun getActionType(actionType: Constants.Swipe): NavOptions { fun getActionType(actionType: Constants.Swipe): NavOptions {
return when (actionType) { return when (actionType) {
Constants.Swipe.DoubleTap -> { Constants.Swipe.DoubleTap -> {

View File

@@ -34,10 +34,12 @@ import com.github.droidworksstudio.common.isTablet
import com.github.droidworksstudio.common.showLongToast import com.github.droidworksstudio.common.showLongToast
import com.github.droidworksstudio.common.showShortToast import com.github.droidworksstudio.common.showShortToast
import com.github.droidworksstudio.launcher.R import com.github.droidworksstudio.launcher.R
import com.github.droidworksstudio.launcher.data.dao.AppInfoDAO
import com.github.droidworksstudio.launcher.databinding.ActivityMainBinding import com.github.droidworksstudio.launcher.databinding.ActivityMainBinding
import com.github.droidworksstudio.launcher.helper.AppHelper import com.github.droidworksstudio.launcher.helper.AppHelper
import com.github.droidworksstudio.launcher.helper.AppReloader import com.github.droidworksstudio.launcher.helper.AppReloader
import com.github.droidworksstudio.launcher.helper.PreferenceHelper import com.github.droidworksstudio.launcher.helper.PreferenceHelper
import com.github.droidworksstudio.launcher.repository.AppInfoRepository
import com.github.droidworksstudio.launcher.utils.Constants import com.github.droidworksstudio.launcher.utils.Constants
import com.github.droidworksstudio.launcher.viewmodel.AppViewModel import com.github.droidworksstudio.launcher.viewmodel.AppViewModel
import com.github.droidworksstudio.launcher.viewmodel.PreferenceViewModel import com.github.droidworksstudio.launcher.viewmodel.PreferenceViewModel
@@ -65,9 +67,15 @@ class MainActivity : AppCompatActivity() {
@Inject @Inject
lateinit var preferenceHelper: PreferenceHelper lateinit var preferenceHelper: PreferenceHelper
@Inject
lateinit var appInfoRepository: AppInfoRepository
@Inject @Inject
lateinit var appHelper: AppHelper lateinit var appHelper: AppHelper
@Inject
lateinit var appDao: AppInfoDAO
private lateinit var sharedPreferences: SharedPreferences private lateinit var sharedPreferences: SharedPreferences
private lateinit var handler: Handler private lateinit var handler: Handler
@@ -96,6 +104,7 @@ class MainActivity : AppCompatActivity() {
setupNavController() setupNavController()
setupOrientation() setupOrientation()
setupLocationManager() setupLocationManager()
setLanguage()
} }
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@@ -406,6 +415,23 @@ class MainActivity : AppCompatActivity() {
} }
applicationContext.showShortToast(getString(R.string.settings_reload_app_backup)) applicationContext.showShortToast(getString(R.string.settings_reload_app_backup))
} }
Constants.BACKUP_WRITE_APPS -> {
data?.data?.also { uri ->
lifecycleScope.launch {
appHelper.backupAppInfo(applicationContext, appDao, uri)
}
}
}
Constants.BACKUP_READ_APPS -> {
data?.data?.also { uri ->
lifecycleScope.launch {
appHelper.restoreAppInfo(applicationContext, appDao, uri)
}
}
AppReloader.restartApp(applicationContext)
}
} }
} }
} }

View File

@@ -12,10 +12,12 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.github.droidworksstudio.common.resetDefaultLauncher import com.github.droidworksstudio.common.resetDefaultLauncher
import com.github.droidworksstudio.launcher.R import com.github.droidworksstudio.launcher.R
import com.github.droidworksstudio.launcher.data.dao.AppInfoDAO
import com.github.droidworksstudio.launcher.databinding.FragmentSettingsAdvancedBinding import com.github.droidworksstudio.launcher.databinding.FragmentSettingsAdvancedBinding
import com.github.droidworksstudio.launcher.helper.AppHelper import com.github.droidworksstudio.launcher.helper.AppHelper
import com.github.droidworksstudio.launcher.helper.AppReloader import com.github.droidworksstudio.launcher.helper.AppReloader
@@ -23,6 +25,7 @@ import com.github.droidworksstudio.launcher.helper.PreferenceHelper
import com.github.droidworksstudio.launcher.listener.ScrollEventListener import com.github.droidworksstudio.launcher.listener.ScrollEventListener
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@@ -38,6 +41,9 @@ class SettingsAdvancedFragment : Fragment(),
@Inject @Inject
lateinit var appHelper: AppHelper lateinit var appHelper: AppHelper
@Inject
lateinit var appDAO: AppInfoDAO
private lateinit var navController: NavController private lateinit var navController: NavController
private lateinit var context: Context private lateinit var context: Context
@@ -125,8 +131,10 @@ class SettingsAdvancedFragment : Fragment(),
// Define the items for the dialog (Backup, Restore, Clear Data) // Define the items for the dialog (Backup, Restore, Clear Data)
val items = arrayOf( val items = arrayOf(
getString(R.string.advanced_settings_backup_restore_backup), getString(R.string.advanced_settings_backup_restore_backup_prefs),
getString(R.string.advanced_settings_backup_restore_restore), getString(R.string.advanced_settings_backup_restore_restore_prefs),
getString(R.string.advanced_settings_backup_restore_backup_apps),
getString(R.string.advanced_settings_backup_restore_restore_apps),
getString(R.string.advanced_settings_backup_restore_clear) getString(R.string.advanced_settings_backup_restore_clear)
) )
@@ -136,6 +144,8 @@ class SettingsAdvancedFragment : Fragment(),
when (which) { when (which) {
0 -> appHelper.storeFile(requireActivity()) 0 -> appHelper.storeFile(requireActivity())
1 -> appHelper.loadFile(requireActivity()) 1 -> appHelper.loadFile(requireActivity())
2 -> appHelper.storeFileApps(requireActivity())
3 -> appHelper.loadFileApps(requireActivity())
else -> confirmClearData() else -> confirmClearData()
} }
} }
@@ -159,6 +169,9 @@ class SettingsAdvancedFragment : Fragment(),
private fun clearData() { private fun clearData() {
preferenceHelper.clearAll(context) preferenceHelper.clearAll(context)
lifecycleScope.launch {
appDAO.clearAll()
}
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
AppReloader.restartApp(context) AppReloader.restartApp(context)
}, 500) }, 500)

View File

@@ -25,6 +25,8 @@ object Constants {
const val LATITUDE = "LATITUDE" const val LATITUDE = "LATITUDE"
const val LONGITUDE = "LONGITUDE" const val LONGITUDE = "LONGITUDE"
const val LOCATION_DENIED = "LOCATION_DENIED"
const val FIRST_LAUNCH = "FIRST_LAUNCH" const val FIRST_LAUNCH = "FIRST_LAUNCH"
const val SHOW_DATE = "SHOW_DATE" const val SHOW_DATE = "SHOW_DATE"
const val SHOW_TIME = "SHOW_TIME" const val SHOW_TIME = "SHOW_TIME"
@@ -34,7 +36,6 @@ object Constants {
const val SHOW_STATUS_BAR = "SHOW_STATUS_BAR" const val SHOW_STATUS_BAR = "SHOW_STATUS_BAR"
const val SHOW_NAVIGATION_BAR = "SHOW_NAVIGATION_BAR" const val SHOW_NAVIGATION_BAR = "SHOW_NAVIGATION_BAR"
const val SHOW_WEATHER_WIDGET = "SHOW_WEATHER_WIDGET" const val SHOW_WEATHER_WIDGET = "SHOW_WEATHER_WIDGET"
const val SHOW_WEATHER_WIDGET_SUN_SET_RISE = "SHOW_WEATHER_WIDGET_SUN_SET_RISE" const val SHOW_WEATHER_WIDGET_SUN_SET_RISE = "SHOW_WEATHER_WIDGET_SUN_SET_RISE"
const val SHOW_BATTERY_WIDGET = "SHOW_BATTERY_WIDGET" const val SHOW_BATTERY_WIDGET = "SHOW_BATTERY_WIDGET"
@@ -114,7 +115,8 @@ object Constants {
const val BACKUP_WRITE = 987 const val BACKUP_WRITE = 987
const val BACKUP_READ = 876 const val BACKUP_READ = 876
const val LOCATION_DENIED = "LOCATION_DENIED" const val BACKUP_WRITE_APPS = 456
const val BACKUP_READ_APPS = 654
const val TRIPLE_TAP_DELAY_MS = 300 const val TRIPLE_TAP_DELAY_MS = 300
const val LONG_PRESS_DELAY_MS = 500 const val LONG_PRESS_DELAY_MS = 500

View File

@@ -41,9 +41,11 @@
<string name="advanced_settings_backup_restore_title">Backup/Restore</string> <string name="advanced_settings_backup_restore_title">Backup/Restore</string>
<string name="advanced_settings_backup_restore_description">Please be careful what you wish for?</string> <string name="advanced_settings_backup_restore_description">Please be careful what you wish for?</string>
<string name="advanced_settings_backup_restore_backup">Backup Preferences</string> <string name="advanced_settings_backup_restore_backup_prefs">Backup Preferences</string>
<string name="advanced_settings_backup_restore_restore">Restore Preferences</string> <string name="advanced_settings_backup_restore_restore_prefs">Restore Preferences</string>
<string name="advanced_settings_backup_restore_clear">Clear Preferences</string> <string name="advanced_settings_backup_restore_backup_apps">Backup Apps</string>
<string name="advanced_settings_backup_restore_restore_apps">Restore Apps</string>
<string name="advanced_settings_backup_restore_clear">Clear All Data</string>
<string name="advanced_settings_backup_restore_clear_title">Clear Preferences</string> <string name="advanced_settings_backup_restore_clear_title">Clear Preferences</string>
<string name="advanced_settings_backup_restore_clear_description">Are you sure you want to do this?</string> <string name="advanced_settings_backup_restore_clear_description">Are you sure you want to do this?</string>