mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
backup to file
This commit is contained in:
parent
e875383191
commit
4f5dee99df
9 changed files with 386 additions and 92 deletions
|
@ -46,6 +46,7 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSet
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||||
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||||
|
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
|
||||||
|
@ -330,7 +331,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
changeStatusBarState(isEmulatorSettings())
|
changeStatusBarState(isEmulatorSettings())
|
||||||
|
|
||||||
// val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
// val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
||||||
|
setUpBackup()
|
||||||
|
|
||||||
CommonActivity.init(this)
|
CommonActivity.init(this)
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,8 @@ import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository
|
import com.lagradost.cloudstream3.ui.APIRepository
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.ChromecastSubtitlesFragment
|
import com.lagradost.cloudstream3.ui.subtitles.ChromecastSubtitlesFragment
|
||||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
||||||
|
import com.lagradost.cloudstream3.utils.BackupUtils.backup
|
||||||
|
import com.lagradost.cloudstream3.utils.BackupUtils.restorePrompt
|
||||||
import com.lagradost.cloudstream3.utils.HOMEPAGE_API
|
import com.lagradost.cloudstream3.utils.HOMEPAGE_API
|
||||||
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
||||||
import com.lagradost.cloudstream3.utils.Qualities
|
import com.lagradost.cloudstream3.utils.Qualities
|
||||||
|
@ -89,7 +91,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
private var beneneCount = 0
|
private var beneneCount = 0
|
||||||
|
|
||||||
// Open file picker
|
// Open file picker
|
||||||
private val pathPicker = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
|
private val pathPicker =
|
||||||
|
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
|
||||||
// It lies, it can be null if file manager quits.
|
// It lies, it can be null if file manager quits.
|
||||||
if (uri == null) return@registerForActivityResult
|
if (uri == null) return@registerForActivityResult
|
||||||
val context = context ?: AcraApplication.context ?: return@registerForActivityResult
|
val context = context ?: AcraApplication.context ?: return@registerForActivityResult
|
||||||
|
@ -173,7 +176,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
private fun showLoginInfo(api: AccountManager, info: OAuth2API.LoginInfo) {
|
private fun showLoginInfo(api: AccountManager, info: OAuth2API.LoginInfo) {
|
||||||
val builder =
|
val builder =
|
||||||
AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom).setView(R.layout.account_managment)
|
AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom)
|
||||||
|
.setView(R.layout.account_managment)
|
||||||
val dialog = builder.show()
|
val dialog = builder.show()
|
||||||
|
|
||||||
dialog.findViewById<ImageView>(R.id.account_profile_picture)?.setImage(info.profilePicture)
|
dialog.findViewById<ImageView>(R.id.account_profile_picture)?.setImage(info.profilePicture)
|
||||||
|
@ -192,29 +196,21 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getPref(id: Int): Preference? {
|
||||||
|
return try {
|
||||||
|
findPreference(getString(id))
|
||||||
|
} catch (e : Exception) {
|
||||||
|
logError(e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
hideKeyboard()
|
hideKeyboard()
|
||||||
setPreferencesFromResource(R.xml.settings, rootKey)
|
setPreferencesFromResource(R.xml.settings, rootKey)
|
||||||
|
|
||||||
val updatePreference = findPreference<Preference>(getString(R.string.manual_check_update_key))!!
|
|
||||||
val localePreference = findPreference<Preference>(getString(R.string.locale_key))!!
|
|
||||||
val benenePreference = findPreference<Preference>(getString(R.string.benene_count))!!
|
|
||||||
val watchQualityPreference = findPreference<Preference>(getString(R.string.quality_pref_key))!!
|
|
||||||
val dnsPreference = findPreference<Preference>(getString(R.string.dns_key))!!
|
|
||||||
val legalPreference = findPreference<Preference>(getString(R.string.legal_notice_key))!!
|
|
||||||
val subdubPreference = findPreference<Preference>(getString(R.string.display_sub_key))!!
|
|
||||||
val providerLangPreference = findPreference<Preference>(getString(R.string.provider_lang_key))!!
|
|
||||||
val downloadPathPreference = findPreference<Preference>(getString(R.string.download_path_key))!!
|
|
||||||
val allLayoutPreference = findPreference<Preference>(getString(R.string.app_layout_key))!!
|
|
||||||
val colorPrimaryPreference = findPreference<Preference>(getString(R.string.primary_color_key))!!
|
|
||||||
val preferedMediaTypePreference = findPreference<Preference>(getString(R.string.prefer_media_type_key))!!
|
|
||||||
val appThemePreference = findPreference<Preference>(getString(R.string.app_theme_key))!!
|
|
||||||
val subPreference = findPreference<Preference>(getString(R.string.subtitle_settings_key))!!
|
|
||||||
val videoCachePreference = findPreference<Preference>(getString(R.string.video_cache_key))!!
|
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext())
|
||||||
val chromecastSubsPreference = findPreference<Preference>(getString(R.string.subtitle_settings_chromecast_key))!!
|
|
||||||
|
|
||||||
videoCachePreference.setOnPreferenceClickListener {
|
getPref(R.string.video_cache_key)?.setOnPreferenceClickListener {
|
||||||
val prefNames = resources.getStringArray(R.array.video_cache_size_names)
|
val prefNames = resources.getStringArray(R.array.video_cache_size_names)
|
||||||
val prefValues = resources.getIntArray(R.array.video_cache_size_values)
|
val prefValues = resources.getIntArray(R.array.video_cache_size_values)
|
||||||
|
|
||||||
|
@ -234,38 +230,37 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
subPreference.setOnPreferenceClickListener {
|
getPref(R.string.subtitle_settings_key)?.setOnPreferenceClickListener {
|
||||||
SubtitlesFragment.push(activity, false)
|
SubtitlesFragment.push(activity, false)
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
chromecastSubsPreference.setOnPreferenceClickListener {
|
getPref(R.string.subtitle_settings_chromecast_key)?.setOnPreferenceClickListener {
|
||||||
ChromecastSubtitlesFragment.push(activity, false)
|
ChromecastSubtitlesFragment.push(activity, false)
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
val syncApis = listOf(Pair(R.string.mal_key, malApi), Pair(R.string.anilist_key, aniListApi))
|
val syncApis =
|
||||||
|
listOf(Pair(R.string.mal_key, malApi), Pair(R.string.anilist_key, aniListApi))
|
||||||
for (sync in syncApis) {
|
for (sync in syncApis) {
|
||||||
findPreference<Preference>(getString(sync.first))?.apply {
|
getPref(sync.first)?.apply {
|
||||||
isVisible = accountEnabled
|
isVisible = accountEnabled
|
||||||
val api = sync.second
|
val api = sync.second
|
||||||
title = getString(R.string.login_format).format(api.name, getString(R.string.account))
|
title =
|
||||||
setOnPreferenceClickListener { pref ->
|
getString(R.string.login_format).format(api.name, getString(R.string.account))
|
||||||
pref.context?.let { ctx ->
|
setOnPreferenceClickListener { _ ->
|
||||||
val info = api.loginInfo()
|
val info = api.loginInfo()
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
showLoginInfo(api, info)
|
showLoginInfo(api, info)
|
||||||
} else {
|
} else {
|
||||||
api.authenticate()
|
api.authenticate()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
legalPreference.setOnPreferenceClickListener {
|
getPref(R.string.legal_notice_key)?.setOnPreferenceClickListener {
|
||||||
val builder: AlertDialog.Builder = AlertDialog.Builder(it.context)
|
val builder: AlertDialog.Builder = AlertDialog.Builder(it.context)
|
||||||
builder.setTitle(R.string.legal_notice)
|
builder.setTitle(R.string.legal_notice)
|
||||||
builder.setMessage(R.string.legal_notice_text)
|
builder.setMessage(R.string.legal_notice_text)
|
||||||
|
@ -273,7 +268,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
subdubPreference.setOnPreferenceClickListener {
|
getPref(R.string.display_sub_key)?.setOnPreferenceClickListener {
|
||||||
activity?.getApiDubstatusSettings()?.let { current ->
|
activity?.getApiDubstatusSettings()?.let { current ->
|
||||||
val dublist = DubStatus.values()
|
val dublist = DubStatus.values()
|
||||||
val names = dublist.map { it.name }
|
val names = dublist.map { it.name }
|
||||||
|
@ -300,7 +295,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
providerLangPreference.setOnPreferenceClickListener {
|
getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener {
|
||||||
activity?.getApiProviderLangSettings()?.let { current ->
|
activity?.getApiProviderLangSettings()?.let { current ->
|
||||||
val allLangs = HashSet<String>()
|
val allLangs = HashSet<String>()
|
||||||
for (api in apis) {
|
for (api in apis) {
|
||||||
|
@ -346,7 +341,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
// app_name_download_path = Cloudstream and does not change depending on release.
|
// app_name_download_path = Cloudstream and does not change depending on release.
|
||||||
// DOES NOT WORK ON SCOPED STORAGE.
|
// DOES NOT WORK ON SCOPED STORAGE.
|
||||||
val secondaryDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) null else Environment.getExternalStorageDirectory().absolutePath +
|
val secondaryDir =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) null else Environment.getExternalStorageDirectory().absolutePath +
|
||||||
File.separator + resources.getString(R.string.app_name_download_path)
|
File.separator + resources.getString(R.string.app_name_download_path)
|
||||||
val first = listOf(defaultDir, secondaryDir)
|
val first = listOf(defaultDir, secondaryDir)
|
||||||
(try {
|
(try {
|
||||||
|
@ -361,11 +357,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadPathPreference.setOnPreferenceClickListener {
|
getPref(R.string.download_path_key)?.setOnPreferenceClickListener {
|
||||||
val dirs = getDownloadDirs()
|
val dirs = getDownloadDirs()
|
||||||
|
|
||||||
val currentDir =
|
val currentDir =
|
||||||
settingsManager.getString(getString(R.string.download_path_pref), null) ?: getDownloadDir().toString()
|
settingsManager.getString(getString(R.string.download_path_pref), null)
|
||||||
|
?: getDownloadDir().toString()
|
||||||
|
|
||||||
activity?.showBottomDialog(
|
activity?.showBottomDialog(
|
||||||
dirs + listOf("Custom"),
|
dirs + listOf("Custom"),
|
||||||
|
@ -384,14 +381,16 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
// Sets both visual and actual paths.
|
// Sets both visual and actual paths.
|
||||||
// key = used path
|
// key = used path
|
||||||
// pref = visual path
|
// pref = visual path
|
||||||
settingsManager.edit().putString(getString(R.string.download_path_key), dirs[it]).apply()
|
settingsManager.edit()
|
||||||
settingsManager.edit().putString(getString(R.string.download_path_pref), dirs[it]).apply()
|
.putString(getString(R.string.download_path_key), dirs[it]).apply()
|
||||||
|
settingsManager.edit()
|
||||||
|
.putString(getString(R.string.download_path_pref), dirs[it]).apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
preferedMediaTypePreference.setOnPreferenceClickListener {
|
getPref(R.string.prefer_media_type_key)?.setOnPreferenceClickListener {
|
||||||
val prefNames = resources.getStringArray(R.array.media_type_pref)
|
val prefNames = resources.getStringArray(R.array.media_type_pref)
|
||||||
val prefValues = resources.getIntArray(R.array.media_type_pref_values)
|
val prefValues = resources.getIntArray(R.array.media_type_pref_values)
|
||||||
|
|
||||||
|
@ -414,7 +413,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
allLayoutPreference.setOnPreferenceClickListener {
|
getPref(R.string.app_layout_key)?.setOnPreferenceClickListener {
|
||||||
val prefNames = resources.getStringArray(R.array.app_layout)
|
val prefNames = resources.getStringArray(R.array.app_layout)
|
||||||
val prefValues = resources.getIntArray(R.array.app_layout_values)
|
val prefValues = resources.getIntArray(R.array.app_layout_values)
|
||||||
|
|
||||||
|
@ -428,7 +427,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
true,
|
true,
|
||||||
{}) {
|
{}) {
|
||||||
try {
|
try {
|
||||||
settingsManager.edit().putInt(getString(R.string.app_layout_key), prefValues[it])
|
settingsManager.edit()
|
||||||
|
.putInt(getString(R.string.app_layout_key), prefValues[it])
|
||||||
.apply()
|
.apply()
|
||||||
activity?.recreate()
|
activity?.recreate()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -438,7 +438,17 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
colorPrimaryPreference.setOnPreferenceClickListener {
|
getPref(R.string.backup_key)?.setOnPreferenceClickListener {
|
||||||
|
activity?.backup()
|
||||||
|
return@setOnPreferenceClickListener true
|
||||||
|
}
|
||||||
|
|
||||||
|
getPref(R.string.restore_key)?.setOnPreferenceClickListener {
|
||||||
|
activity?.restorePrompt()
|
||||||
|
return@setOnPreferenceClickListener true
|
||||||
|
}
|
||||||
|
|
||||||
|
getPref(R.string.primary_color_key)?.setOnPreferenceClickListener {
|
||||||
val prefNames = resources.getStringArray(R.array.themes_overlay_names)
|
val prefNames = resources.getStringArray(R.array.themes_overlay_names)
|
||||||
val prefValues = resources.getStringArray(R.array.themes_overlay_names_values)
|
val prefValues = resources.getStringArray(R.array.themes_overlay_names_values)
|
||||||
|
|
||||||
|
@ -452,7 +462,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
true,
|
true,
|
||||||
{}) {
|
{}) {
|
||||||
try {
|
try {
|
||||||
settingsManager.edit().putString(getString(R.string.primary_color_key), prefValues[it])
|
settingsManager.edit()
|
||||||
|
.putString(getString(R.string.primary_color_key), prefValues[it])
|
||||||
.apply()
|
.apply()
|
||||||
activity?.recreate()
|
activity?.recreate()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -462,7 +473,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
appThemePreference.setOnPreferenceClickListener {
|
getPref(R.string.app_theme_key)?.setOnPreferenceClickListener {
|
||||||
val prefNames = resources.getStringArray(R.array.themes_names)
|
val prefNames = resources.getStringArray(R.array.themes_names)
|
||||||
val prefValues = resources.getStringArray(R.array.themes_names_values)
|
val prefValues = resources.getStringArray(R.array.themes_names_values)
|
||||||
|
|
||||||
|
@ -476,7 +487,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
true,
|
true,
|
||||||
{}) {
|
{}) {
|
||||||
try {
|
try {
|
||||||
settingsManager.edit().putString(getString(R.string.app_theme_key), prefValues[it])
|
settingsManager.edit()
|
||||||
|
.putString(getString(R.string.app_theme_key), prefValues[it])
|
||||||
.apply()
|
.apply()
|
||||||
activity?.recreate()
|
activity?.recreate()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -486,7 +498,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
watchQualityPreference.setOnPreferenceClickListener {
|
getPref(R.string.quality_pref_key)?.setOnPreferenceClickListener {
|
||||||
val prefNames = resources.getStringArray(R.array.quality_pref)
|
val prefNames = resources.getStringArray(R.array.quality_pref)
|
||||||
val prefValues = resources.getIntArray(R.array.quality_pref_values)
|
val prefValues = resources.getIntArray(R.array.quality_pref_values)
|
||||||
|
|
||||||
|
@ -508,7 +520,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsPreference.setOnPreferenceClickListener {
|
getPref(R.string.dns_key)?.setOnPreferenceClickListener {
|
||||||
val prefNames = resources.getStringArray(R.array.dns_pref)
|
val prefNames = resources.getStringArray(R.array.dns_pref)
|
||||||
val prefValues = resources.getIntArray(R.array.dns_pref_values)
|
val prefValues = resources.getIntArray(R.array.dns_pref_values)
|
||||||
|
|
||||||
|
@ -529,16 +541,19 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
beneneCount = settingsManager.getInt(getString(R.string.benene_count), 0)
|
beneneCount = settingsManager.getInt(getString(R.string.benene_count), 0)
|
||||||
|
getPref(R.string.benene_count)?.let { pref ->
|
||||||
benenePreference.summary =
|
pref.summary =
|
||||||
if (beneneCount <= 0) getString(R.string.benene_count_text_none) else getString(R.string.benene_count_text).format(
|
if (beneneCount <= 0) getString(R.string.benene_count_text_none) else getString(
|
||||||
|
R.string.benene_count_text
|
||||||
|
).format(
|
||||||
beneneCount
|
beneneCount
|
||||||
)
|
)
|
||||||
|
|
||||||
benenePreference.setOnPreferenceClickListener {
|
pref.setOnPreferenceClickListener {
|
||||||
try {
|
try {
|
||||||
beneneCount++
|
beneneCount++
|
||||||
settingsManager.edit().putInt(getString(R.string.benene_count), beneneCount).apply()
|
settingsManager.edit().putInt(getString(R.string.benene_count), beneneCount)
|
||||||
|
.apply()
|
||||||
it.summary = getString(R.string.benene_count_text).format(beneneCount)
|
it.summary = getString(R.string.benene_count_text).format(beneneCount)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
|
@ -546,11 +561,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePreference.setOnPreferenceClickListener {
|
getPref(R.string.manual_check_update_key)?.setOnPreferenceClickListener {
|
||||||
thread {
|
thread {
|
||||||
if (!requireActivity().runAutoUpdate(false)) {
|
if (!requireActivity().runAutoUpdate(false)) {
|
||||||
activity?.runOnUiThread {
|
activity?.runOnUiThread {
|
||||||
|
@ -561,7 +577,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||||
return@setOnPreferenceClickListener true
|
return@setOnPreferenceClickListener true
|
||||||
}
|
}
|
||||||
|
|
||||||
localePreference.setOnPreferenceClickListener { pref ->
|
getPref(R.string.locale_key)?.setOnPreferenceClickListener { pref ->
|
||||||
val tempLangs = languages.toMutableList()
|
val tempLangs = languages.toMutableList()
|
||||||
if (beneneCount > 100) {
|
if (beneneCount > 100) {
|
||||||
tempLangs.add(Triple("\uD83E\uDD8D", "mmmm... monke", "mo"))
|
tempLangs.add(Triple("\uD83E\uDD8D", "mmmm... monke", "mo"))
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
|
import com.lagradost.cloudstream3.R
|
||||||
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.getDefaultSharedPrefs
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.getSharedPrefs
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.mapper
|
||||||
|
import com.lagradost.cloudstream3.utils.DataStore.setKeyRaw
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
||||||
|
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath
|
||||||
|
import com.lagradost.cloudstream3.utils.VideoDownloadManager.isDownloadDir
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.lang.System.currentTimeMillis
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object BackupUtils {
|
||||||
|
var restoreFileSelector: ActivityResultLauncher<String>? = null
|
||||||
|
|
||||||
|
// Kinda hack, but I couldn't think of a better way
|
||||||
|
data class BackupVars(
|
||||||
|
@JsonProperty("_Bool") val _Bool: Map<String, Boolean>?,
|
||||||
|
@JsonProperty("_Int") val _Int: Map<String, Int>?,
|
||||||
|
@JsonProperty("_String") val _String: Map<String, String>?,
|
||||||
|
@JsonProperty("_Float") val _Float: Map<String, Float>?,
|
||||||
|
@JsonProperty("_Long") val _Long: Map<String, Long>?,
|
||||||
|
@JsonProperty("_StringSet") val _StringSet: Map<String, Set<String>?>?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class BackupFile(
|
||||||
|
@JsonProperty("datastore") val datastore: BackupVars,
|
||||||
|
@JsonProperty("settings") val settings: BackupVars
|
||||||
|
)
|
||||||
|
|
||||||
|
fun FragmentActivity.backup() {
|
||||||
|
try {
|
||||||
|
if (checkWrite()) {
|
||||||
|
val subDir = getBasePath().first
|
||||||
|
val date = SimpleDateFormat("yyyy_MM_dd_HH_mm").format(Date(currentTimeMillis()))
|
||||||
|
val ext = "json"
|
||||||
|
val displayName = "CS3_Backup_${date}"
|
||||||
|
|
||||||
|
val allData = getSharedPrefs().all
|
||||||
|
val allSettings = getDefaultSharedPrefs().all
|
||||||
|
|
||||||
|
val allDataSorted = BackupVars(
|
||||||
|
allData.filter { it.value is Boolean } as? Map<String, Boolean>,
|
||||||
|
allData.filter { it.value is Int } as? Map<String, Int>,
|
||||||
|
allData.filter { it.value is String } as? Map<String, String>,
|
||||||
|
allData.filter { it.value is Float } as? Map<String, Float>,
|
||||||
|
allData.filter { it.value is Long } as? Map<String, Long>,
|
||||||
|
allData.filter { it.value as? Set<String> != null } as? Map<String, Set<String>>
|
||||||
|
)
|
||||||
|
|
||||||
|
val allSettingsSorted = BackupVars(
|
||||||
|
allSettings.filter { it.value is Boolean } as? Map<String, Boolean>,
|
||||||
|
allSettings.filter { it.value is Int } as? Map<String, Int>,
|
||||||
|
allSettings.filter { it.value is String } as? Map<String, String>,
|
||||||
|
allSettings.filter { it.value is Float } as? Map<String, Float>,
|
||||||
|
allSettings.filter { it.value is Long } as? Map<String, Long>,
|
||||||
|
allSettings.filter { it.value as? Set<String> != null } as? Map<String, Set<String>>
|
||||||
|
)
|
||||||
|
|
||||||
|
val backupFile = BackupFile(
|
||||||
|
allDataSorted,
|
||||||
|
allSettingsSorted
|
||||||
|
)
|
||||||
|
val steam =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && subDir?.isDownloadDir() == true) {
|
||||||
|
val cr = this.contentResolver
|
||||||
|
val contentUri =
|
||||||
|
MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) // USE INSTEAD OF MediaStore.Downloads.EXTERNAL_CONTENT_URI
|
||||||
|
//val currentMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||||
|
|
||||||
|
val newFile = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
|
||||||
|
put(MediaStore.MediaColumns.TITLE, displayName)
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, "application/json")
|
||||||
|
//put(MediaStore.MediaColumns.RELATIVE_PATH, folder)
|
||||||
|
}
|
||||||
|
|
||||||
|
val newFileUri = cr.insert(
|
||||||
|
contentUri,
|
||||||
|
newFile
|
||||||
|
) ?: throw IOException("Error creating file uri")
|
||||||
|
cr.openOutputStream(newFileUri, "w")
|
||||||
|
?: throw IOException("Error opening stream")
|
||||||
|
} else {
|
||||||
|
val fileName = "$displayName.$ext"
|
||||||
|
val rFile = subDir?.findFile(fileName)
|
||||||
|
if (rFile?.exists() == true) {
|
||||||
|
rFile.delete()
|
||||||
|
}
|
||||||
|
val file =
|
||||||
|
subDir?.createFile(fileName)
|
||||||
|
?: throw IOException("Error creating file")
|
||||||
|
if (!file.exists()) throw IOException("File does not exist")
|
||||||
|
file.openOutputStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
val printStream = PrintWriter(steam)
|
||||||
|
printStream.print(mapper.writeValueAsString(backupFile))
|
||||||
|
printStream.close()
|
||||||
|
|
||||||
|
showToast(
|
||||||
|
this,
|
||||||
|
R.string.backup_success,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
showToast(this, getString(R.string.backup_failed), Toast.LENGTH_LONG)
|
||||||
|
requestRW()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
try {
|
||||||
|
showToast(
|
||||||
|
this,
|
||||||
|
getString(R.string.backup_failed_error_format).format(e.toString()),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FragmentActivity.setUpBackup() {
|
||||||
|
try {
|
||||||
|
restoreFileSelector =
|
||||||
|
registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
|
||||||
|
this.let { activity ->
|
||||||
|
uri?.let {
|
||||||
|
try {
|
||||||
|
val input =
|
||||||
|
activity.contentResolver.openInputStream(uri)
|
||||||
|
?: return@registerForActivityResult
|
||||||
|
|
||||||
|
val restoredValue =
|
||||||
|
mapper.readValue<BackupFile>(input)
|
||||||
|
activity.restore(
|
||||||
|
restoredValue,
|
||||||
|
restoreSettings = true,
|
||||||
|
restoreDataStore = true
|
||||||
|
)
|
||||||
|
activity.recreate()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
try { // smth can fail in .format
|
||||||
|
showToast(
|
||||||
|
activity,
|
||||||
|
getString(R.string.restore_failed_format).format(e.toString())
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FragmentActivity.restorePrompt() {
|
||||||
|
runOnUiThread {
|
||||||
|
restoreFileSelector?.launch("application/json")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> Context.restoreMap(
|
||||||
|
map: Map<String, T>?,
|
||||||
|
isEditingAppSettings: Boolean = false
|
||||||
|
) {
|
||||||
|
map?.forEach {
|
||||||
|
setKeyRaw(it.key, it.value, isEditingAppSettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.restore(
|
||||||
|
backupFile: BackupFile,
|
||||||
|
restoreSettings: Boolean,
|
||||||
|
restoreDataStore: Boolean
|
||||||
|
) {
|
||||||
|
if (restoreSettings) {
|
||||||
|
restoreMap(backupFile.settings._Bool, true)
|
||||||
|
restoreMap(backupFile.settings._Int, true)
|
||||||
|
restoreMap(backupFile.settings._String, true)
|
||||||
|
restoreMap(backupFile.settings._Float, true)
|
||||||
|
restoreMap(backupFile.settings._Long, true)
|
||||||
|
restoreMap(backupFile.settings._StringSet, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restoreDataStore) {
|
||||||
|
restoreMap(backupFile.datastore._Bool)
|
||||||
|
restoreMap(backupFile.datastore._Int)
|
||||||
|
restoreMap(backupFile.datastore._String)
|
||||||
|
restoreMap(backupFile.datastore._Float)
|
||||||
|
restoreMap(backupFile.datastore._Long)
|
||||||
|
restoreMap(backupFile.datastore._StringSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||||
|
@ -32,6 +33,24 @@ object DataStore {
|
||||||
return "${folder}/${path}"
|
return "${folder}/${path}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> Context.setKeyRaw(path: String, value: T, isEditingAppSettings: Boolean = false) {
|
||||||
|
val editor: SharedPreferences.Editor =
|
||||||
|
if (isEditingAppSettings) getDefaultSharedPrefs().edit() else getSharedPrefs().edit()
|
||||||
|
when (value) {
|
||||||
|
is Boolean -> editor.putBoolean(path, value)
|
||||||
|
is Int -> editor.putInt(path, value)
|
||||||
|
is String -> editor.putString(path, value)
|
||||||
|
is Float -> editor.putFloat(path, value)
|
||||||
|
is Long -> editor.putLong(path, value)
|
||||||
|
(value as? Set<String> != null) -> editor.putStringSet(path, value as Set<String>)
|
||||||
|
}
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getDefaultSharedPrefs(): SharedPreferences {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(this)
|
||||||
|
}
|
||||||
|
|
||||||
fun Context.getKeys(folder: String): List<String> {
|
fun Context.getKeys(folder: String): List<String> {
|
||||||
return this.getSharedPrefs().all.keys.filter { it.startsWith(folder) }
|
return this.getSharedPrefs().all.keys.filter { it.startsWith(folder) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1043,7 +1043,7 @@ object VideoDownloadManager {
|
||||||
return basePathToFile(this, basePathSetting) to basePathSetting
|
return basePathToFile(this, basePathSetting) to basePathSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun UniFile?.isDownloadDir(): Boolean {
|
fun UniFile?.isDownloadDir(): Boolean {
|
||||||
return this != null && this.filePath == getDownloadDir()?.filePath
|
return this != null && this.filePath == getDownloadDir()?.filePath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
9
app/src/main/res/drawable/baseline_restore_page_24.xml
Normal file
9
app/src/main/res/drawable/baseline_restore_page_24.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24" android:tint="?attr/white">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM12,18c-2.05,0 -3.81,-1.24 -4.58,-3h1.71c0.63,0.9 1.68,1.5 2.87,1.5 1.93,0 3.5,-1.57 3.5,-3.5S13.93,9.5 12,9.5c-1.35,0 -2.52,0.78 -3.1,1.9l1.6,1.6h-4L6.5,9l1.3,1.3C8.69,8.92 10.23,8 12,8c2.76,0 5,2.24 5,5s-2.24,5 -5,5z"/>
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/baseline_save_as_24.xml
Normal file
10
app/src/main/res/drawable/baseline_save_as_24.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/white">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M21,12.4V7l-4,-4H5C3.89,3 3,3.9 3,5v14c0,1.1 0.89,2 2,2h7.4L21,12.4zM15,15c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3s1.34,-3 3,-3S15,13.34 15,15zM6,6h9v4H6V6zM19.99,16.25l1.77,1.77L16.77,23H15v-1.77L19.99,16.25zM23.25,16.51l-0.85,0.85l-1.77,-1.77l0.85,-0.85c0.2,-0.2 0.51,-0.2 0.71,0l1.06,1.06C23.45,16 23.45,16.32 23.25,16.51z"/>
|
||||||
|
</vector>
|
|
@ -31,6 +31,8 @@
|
||||||
<string name="app_name_download_path" translatable="false">Cloudstream</string>
|
<string name="app_name_download_path" translatable="false">Cloudstream</string>
|
||||||
<string name="app_layout_key" translatable="false">app_layout_key</string>
|
<string name="app_layout_key" translatable="false">app_layout_key</string>
|
||||||
<string name="primary_color_key" translatable="false">primary_color_key</string>
|
<string name="primary_color_key" translatable="false">primary_color_key</string>
|
||||||
|
<string name="restore_key" translatable="false">restore_key</string>
|
||||||
|
<string name="backup_key" translatable="false">backup_key</string>
|
||||||
<string name="prefer_media_type_key" translatable="false">prefer_media_type_key</string>
|
<string name="prefer_media_type_key" translatable="false">prefer_media_type_key</string>
|
||||||
<string name="app_theme_key" translatable="false">app_theme_key</string>
|
<string name="app_theme_key" translatable="false">app_theme_key</string>
|
||||||
|
|
||||||
|
@ -195,6 +197,14 @@
|
||||||
overlay
|
overlay
|
||||||
</string>
|
</string>
|
||||||
|
|
||||||
|
<string name="restore_settings">Restore data from backup</string>
|
||||||
|
<string name="backup_settings">Backup data</string>
|
||||||
|
<string name="restore_success">Loaded backup file</string>
|
||||||
|
<string name="restore_failed_format" formatted="true">Failed to restore data from file %s</string>
|
||||||
|
<string name="backup_success">Successfully stored data</string>
|
||||||
|
<string name="backup_failed">Storage permissions missing, please try again</string>
|
||||||
|
<string name="backup_failed_error_format">Error backing up %s</string>
|
||||||
|
|
||||||
<string name="search">Search</string>
|
<string name="search">Search</string>
|
||||||
<string name="settings_info">Info</string>
|
<string name="settings_info">Info</string>
|
||||||
<string name="advanced_search">Advanced Search</string>
|
<string name="advanced_search">Advanced Search</string>
|
||||||
|
|
|
@ -169,6 +169,16 @@
|
||||||
app:key="@string/manual_check_update_key"
|
app:key="@string/manual_check_update_key"
|
||||||
app:icon="@drawable/ic_baseline_system_update_24" />
|
app:icon="@drawable/ic_baseline_system_update_24" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:icon="@drawable/baseline_save_as_24"
|
||||||
|
android:key="@string/backup_key"
|
||||||
|
android:title="@string/backup_settings" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:icon="@drawable/baseline_restore_page_24"
|
||||||
|
android:key="@string/restore_key"
|
||||||
|
android:title="@string/restore_settings" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:key="@string/mal_key"
|
android:key="@string/mal_key"
|
||||||
android:icon="@drawable/mal_logo" />
|
android:icon="@drawable/mal_logo" />
|
||||||
|
|
Loading…
Reference in a new issue