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.loadCache
 | 
			
		||||
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.removeKey
 | 
			
		||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
 | 
			
		||||
| 
						 | 
				
			
			@ -330,7 +331,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
 | 
			
		|||
        changeStatusBarState(isEmulatorSettings())
 | 
			
		||||
 | 
			
		||||
        //  val navView: BottomNavigationView = findViewById(R.id.nav_view)
 | 
			
		||||
 | 
			
		||||
        setUpBackup()
 | 
			
		||||
 | 
			
		||||
        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.subtitles.ChromecastSubtitlesFragment
 | 
			
		||||
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.InAppUpdater.Companion.runAutoUpdate
 | 
			
		||||
import com.lagradost.cloudstream3.utils.Qualities
 | 
			
		||||
| 
						 | 
				
			
			@ -57,7 +59,7 @@ import kotlin.concurrent.thread
 | 
			
		|||
 | 
			
		||||
class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		||||
    companion object {
 | 
			
		||||
        private fun Context.getLayoutInt() : Int {
 | 
			
		||||
        private fun Context.getLayoutInt(): Int {
 | 
			
		||||
            val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
 | 
			
		||||
            return settingsManager.getInt(this.getString(R.string.app_layout_key), -1)
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -70,11 +72,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            return value == 1 || value == 2
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun Context.isTrueTvSettings() : Boolean {
 | 
			
		||||
        fun Context.isTrueTvSettings(): Boolean {
 | 
			
		||||
            return getLayoutInt() == 1
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        fun Context.isEmulatorSettings() : Boolean {
 | 
			
		||||
        fun Context.isEmulatorSettings(): Boolean {
 | 
			
		||||
            return getLayoutInt() == 2
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -89,31 +91,32 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
    private var beneneCount = 0
 | 
			
		||||
 | 
			
		||||
    // Open file picker
 | 
			
		||||
    private val pathPicker = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
 | 
			
		||||
        // It lies, it can be null if file manager quits.
 | 
			
		||||
        if (uri == null) return@registerForActivityResult
 | 
			
		||||
        val context = context ?: AcraApplication.context ?: return@registerForActivityResult
 | 
			
		||||
        // RW perms for the path
 | 
			
		||||
        val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
 | 
			
		||||
                Intent.FLAG_GRANT_WRITE_URI_PERMISSION
 | 
			
		||||
    private val pathPicker =
 | 
			
		||||
        registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
 | 
			
		||||
            // It lies, it can be null if file manager quits.
 | 
			
		||||
            if (uri == null) return@registerForActivityResult
 | 
			
		||||
            val context = context ?: AcraApplication.context ?: return@registerForActivityResult
 | 
			
		||||
            // RW perms for the path
 | 
			
		||||
            val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
 | 
			
		||||
                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION
 | 
			
		||||
 | 
			
		||||
        context.contentResolver.takePersistableUriPermission(uri, flags)
 | 
			
		||||
            context.contentResolver.takePersistableUriPermission(uri, flags)
 | 
			
		||||
 | 
			
		||||
        val file = UniFile.fromUri(context, uri)
 | 
			
		||||
        println("Selected URI path: $uri - Full path: ${file.filePath}")
 | 
			
		||||
            val file = UniFile.fromUri(context, uri)
 | 
			
		||||
            println("Selected URI path: $uri - Full path: ${file.filePath}")
 | 
			
		||||
 | 
			
		||||
        // Stores the real URI using download_path_key
 | 
			
		||||
        // Important that the URI is stored instead of filepath due to permissions.
 | 
			
		||||
        PreferenceManager.getDefaultSharedPreferences(context)
 | 
			
		||||
            .edit().putString(getString(R.string.download_path_key), uri.toString()).apply()
 | 
			
		||||
 | 
			
		||||
        // From URI -> File path
 | 
			
		||||
        // File path here is purely for cosmetic purposes in settings
 | 
			
		||||
        (file.filePath ?: uri.toString()).let {
 | 
			
		||||
            // Stores the real URI using download_path_key
 | 
			
		||||
            // Important that the URI is stored instead of filepath due to permissions.
 | 
			
		||||
            PreferenceManager.getDefaultSharedPreferences(context)
 | 
			
		||||
                .edit().putString(getString(R.string.download_path_pref), it).apply()
 | 
			
		||||
                .edit().putString(getString(R.string.download_path_key), uri.toString()).apply()
 | 
			
		||||
 | 
			
		||||
            // From URI -> File path
 | 
			
		||||
            // File path here is purely for cosmetic purposes in settings
 | 
			
		||||
            (file.filePath ?: uri.toString()).let {
 | 
			
		||||
                PreferenceManager.getDefaultSharedPreferences(context)
 | 
			
		||||
                    .edit().putString(getString(R.string.download_path_pref), it).apply()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // idk, if you find a way of automating this it would be great
 | 
			
		||||
    // https://www.iemoji.com/view/emoji/1794/flags/antarctica
 | 
			
		||||
| 
						 | 
				
			
			@ -173,7 +176,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
 | 
			
		||||
    private fun showLoginInfo(api: AccountManager, info: OAuth2API.LoginInfo) {
 | 
			
		||||
        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()
 | 
			
		||||
 | 
			
		||||
        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?) {
 | 
			
		||||
        hideKeyboard()
 | 
			
		||||
        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 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 prefValues = resources.getIntArray(R.array.video_cache_size_values)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -234,38 +230,37 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        subPreference.setOnPreferenceClickListener {
 | 
			
		||||
        getPref(R.string.subtitle_settings_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            SubtitlesFragment.push(activity, false)
 | 
			
		||||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        chromecastSubsPreference.setOnPreferenceClickListener {
 | 
			
		||||
        getPref(R.string.subtitle_settings_chromecast_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            ChromecastSubtitlesFragment.push(activity, false)
 | 
			
		||||
            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) {
 | 
			
		||||
            findPreference<Preference>(getString(sync.first))?.apply {
 | 
			
		||||
            getPref(sync.first)?.apply {
 | 
			
		||||
                isVisible = accountEnabled
 | 
			
		||||
                val api = sync.second
 | 
			
		||||
                title = getString(R.string.login_format).format(api.name, getString(R.string.account))
 | 
			
		||||
                setOnPreferenceClickListener { pref ->
 | 
			
		||||
                    pref.context?.let { ctx ->
 | 
			
		||||
                        val info = api.loginInfo()
 | 
			
		||||
                        if (info != null) {
 | 
			
		||||
                            showLoginInfo(api, info)
 | 
			
		||||
                        } else {
 | 
			
		||||
                            api.authenticate()
 | 
			
		||||
                        }
 | 
			
		||||
                title =
 | 
			
		||||
                    getString(R.string.login_format).format(api.name, getString(R.string.account))
 | 
			
		||||
                setOnPreferenceClickListener { _ ->
 | 
			
		||||
                    val info = api.loginInfo()
 | 
			
		||||
                    if (info != null) {
 | 
			
		||||
                        showLoginInfo(api, info)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        api.authenticate()
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return@setOnPreferenceClickListener true
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        legalPreference.setOnPreferenceClickListener {
 | 
			
		||||
        getPref(R.string.legal_notice_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            val builder: AlertDialog.Builder = AlertDialog.Builder(it.context)
 | 
			
		||||
            builder.setTitle(R.string.legal_notice)
 | 
			
		||||
            builder.setMessage(R.string.legal_notice_text)
 | 
			
		||||
| 
						 | 
				
			
			@ -273,7 +268,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        subdubPreference.setOnPreferenceClickListener {
 | 
			
		||||
        getPref(R.string.display_sub_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            activity?.getApiDubstatusSettings()?.let { current ->
 | 
			
		||||
                val dublist = DubStatus.values()
 | 
			
		||||
                val names = dublist.map { it.name }
 | 
			
		||||
| 
						 | 
				
			
			@ -300,7 +295,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        providerLangPreference.setOnPreferenceClickListener {
 | 
			
		||||
        getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            activity?.getApiProviderLangSettings()?.let { current ->
 | 
			
		||||
                val allLangs = HashSet<String>()
 | 
			
		||||
                for (api in apis) {
 | 
			
		||||
| 
						 | 
				
			
			@ -346,8 +341,9 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
 | 
			
		||||
                // app_name_download_path = Cloudstream and does not change depending on release.
 | 
			
		||||
                // DOES NOT WORK ON SCOPED STORAGE.
 | 
			
		||||
                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)
 | 
			
		||||
                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)
 | 
			
		||||
                val first = listOf(defaultDir, secondaryDir)
 | 
			
		||||
                (try {
 | 
			
		||||
                    val currentDir = context?.getBasePath()?.let { it.first?.filePath ?: it.second }
 | 
			
		||||
| 
						 | 
				
			
			@ -361,11 +357,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            } ?: emptyList()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        downloadPathPreference.setOnPreferenceClickListener {
 | 
			
		||||
        getPref(R.string.download_path_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            val dirs = getDownloadDirs()
 | 
			
		||||
 | 
			
		||||
            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(
 | 
			
		||||
                dirs + listOf("Custom"),
 | 
			
		||||
| 
						 | 
				
			
			@ -377,21 +374,23 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
                if (it == dirs.size) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        pathPicker.launch(Uri.EMPTY)
 | 
			
		||||
                    } catch (e : Exception) {
 | 
			
		||||
                    } catch (e: Exception) {
 | 
			
		||||
                        logError(e)
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Sets both visual and actual paths.
 | 
			
		||||
                    // key = used path
 | 
			
		||||
                    // pref = visual path
 | 
			
		||||
                    settingsManager.edit().putString(getString(R.string.download_path_key), dirs[it]).apply()
 | 
			
		||||
                    settingsManager.edit().putString(getString(R.string.download_path_pref), dirs[it]).apply()
 | 
			
		||||
                    settingsManager.edit()
 | 
			
		||||
                        .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
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        preferedMediaTypePreference.setOnPreferenceClickListener {
 | 
			
		||||
        getPref(R.string.prefer_media_type_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            val prefNames = resources.getStringArray(R.array.media_type_pref)
 | 
			
		||||
            val prefValues = resources.getIntArray(R.array.media_type_pref_values)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -414,7 +413,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        allLayoutPreference.setOnPreferenceClickListener {
 | 
			
		||||
        getPref(R.string.app_layout_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            val prefNames = resources.getStringArray(R.array.app_layout)
 | 
			
		||||
            val prefValues = resources.getIntArray(R.array.app_layout_values)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -428,7 +427,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
                true,
 | 
			
		||||
                {}) {
 | 
			
		||||
                try {
 | 
			
		||||
                    settingsManager.edit().putInt(getString(R.string.app_layout_key), prefValues[it])
 | 
			
		||||
                    settingsManager.edit()
 | 
			
		||||
                        .putInt(getString(R.string.app_layout_key), prefValues[it])
 | 
			
		||||
                        .apply()
 | 
			
		||||
                    activity?.recreate()
 | 
			
		||||
                } catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -438,7 +438,17 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            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 prefValues = resources.getStringArray(R.array.themes_overlay_names_values)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -452,7 +462,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
                true,
 | 
			
		||||
                {}) {
 | 
			
		||||
                try {
 | 
			
		||||
                    settingsManager.edit().putString(getString(R.string.primary_color_key), prefValues[it])
 | 
			
		||||
                    settingsManager.edit()
 | 
			
		||||
                        .putString(getString(R.string.primary_color_key), prefValues[it])
 | 
			
		||||
                        .apply()
 | 
			
		||||
                    activity?.recreate()
 | 
			
		||||
                } catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -462,7 +473,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        appThemePreference.setOnPreferenceClickListener {
 | 
			
		||||
        getPref(R.string.app_theme_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            val prefNames = resources.getStringArray(R.array.themes_names)
 | 
			
		||||
            val prefValues = resources.getStringArray(R.array.themes_names_values)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -476,7 +487,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
                true,
 | 
			
		||||
                {}) {
 | 
			
		||||
                try {
 | 
			
		||||
                    settingsManager.edit().putString(getString(R.string.app_theme_key), prefValues[it])
 | 
			
		||||
                    settingsManager.edit()
 | 
			
		||||
                        .putString(getString(R.string.app_theme_key), prefValues[it])
 | 
			
		||||
                        .apply()
 | 
			
		||||
                    activity?.recreate()
 | 
			
		||||
                } catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -486,7 +498,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        watchQualityPreference.setOnPreferenceClickListener {
 | 
			
		||||
        getPref(R.string.quality_pref_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            val prefNames = resources.getStringArray(R.array.quality_pref)
 | 
			
		||||
            val prefValues = resources.getIntArray(R.array.quality_pref_values)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -508,7 +520,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        dnsPreference.setOnPreferenceClickListener {
 | 
			
		||||
        getPref(R.string.dns_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            val prefNames = resources.getStringArray(R.array.dns_pref)
 | 
			
		||||
            val prefValues = resources.getIntArray(R.array.dns_pref_values)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -529,28 +541,32 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
 | 
			
		||||
        try {
 | 
			
		||||
            beneneCount = settingsManager.getInt(getString(R.string.benene_count), 0)
 | 
			
		||||
            getPref(R.string.benene_count)?.let { pref ->
 | 
			
		||||
                pref.summary =
 | 
			
		||||
                    if (beneneCount <= 0) getString(R.string.benene_count_text_none) else getString(
 | 
			
		||||
                        R.string.benene_count_text
 | 
			
		||||
                    ).format(
 | 
			
		||||
                        beneneCount
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
            benenePreference.summary =
 | 
			
		||||
                if (beneneCount <= 0) getString(R.string.benene_count_text_none) else getString(R.string.benene_count_text).format(
 | 
			
		||||
                    beneneCount
 | 
			
		||||
                )
 | 
			
		||||
                pref.setOnPreferenceClickListener {
 | 
			
		||||
                    try {
 | 
			
		||||
                        beneneCount++
 | 
			
		||||
                        settingsManager.edit().putInt(getString(R.string.benene_count), beneneCount)
 | 
			
		||||
                            .apply()
 | 
			
		||||
                        it.summary = getString(R.string.benene_count_text).format(beneneCount)
 | 
			
		||||
                    } catch (e: Exception) {
 | 
			
		||||
                        logError(e)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
            benenePreference.setOnPreferenceClickListener {
 | 
			
		||||
                try {
 | 
			
		||||
                    beneneCount++
 | 
			
		||||
                    settingsManager.edit().putInt(getString(R.string.benene_count), beneneCount).apply()
 | 
			
		||||
                    it.summary = getString(R.string.benene_count_text).format(beneneCount)
 | 
			
		||||
                } catch (e: Exception) {
 | 
			
		||||
                    logError(e)
 | 
			
		||||
                    return@setOnPreferenceClickListener true
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return@setOnPreferenceClickListener true
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: Exception) {
 | 
			
		||||
            e.printStackTrace()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        updatePreference.setOnPreferenceClickListener {
 | 
			
		||||
        getPref(R.string.manual_check_update_key)?.setOnPreferenceClickListener {
 | 
			
		||||
            thread {
 | 
			
		||||
                if (!requireActivity().runAutoUpdate(false)) {
 | 
			
		||||
                    activity?.runOnUiThread {
 | 
			
		||||
| 
						 | 
				
			
			@ -561,7 +577,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
 | 
			
		|||
            return@setOnPreferenceClickListener true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        localePreference.setOnPreferenceClickListener { pref ->
 | 
			
		||||
        getPref(R.string.locale_key)?.setOnPreferenceClickListener { pref ->
 | 
			
		||||
            val tempLangs = languages.toMutableList()
 | 
			
		||||
            if (beneneCount > 100) {
 | 
			
		||||
                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.SharedPreferences
 | 
			
		||||
import androidx.preference.PreferenceManager
 | 
			
		||||
import com.fasterxml.jackson.databind.DeserializationFeature
 | 
			
		||||
import com.fasterxml.jackson.databind.json.JsonMapper
 | 
			
		||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +33,24 @@ object DataStore {
 | 
			
		|||
        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> {
 | 
			
		||||
        return this.getSharedPrefs().all.keys.filter { it.startsWith(folder) }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1043,7 +1043,7 @@ object VideoDownloadManager {
 | 
			
		|||
        return basePathToFile(this, basePathSetting) to basePathSetting
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun UniFile?.isDownloadDir(): Boolean {
 | 
			
		||||
    fun UniFile?.isDownloadDir(): Boolean {
 | 
			
		||||
        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_layout_key" translatable="false">app_layout_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="app_theme_key" translatable="false">app_theme_key</string>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -195,6 +197,14 @@
 | 
			
		|||
        overlay
 | 
			
		||||
    </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="settings_info">Info</string>
 | 
			
		||||
    <string name="advanced_search">Advanced Search</string>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -169,6 +169,16 @@
 | 
			
		|||
                app:key="@string/manual_check_update_key"
 | 
			
		||||
                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
 | 
			
		||||
                android:key="@string/mal_key"
 | 
			
		||||
                android:icon="@drawable/mal_logo" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue