Feature: Added the ability to backup and restore apps.
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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 -> {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user