forked from recloudstream/cloudstream
		
	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 | ||||||
|  | @ -57,7 +59,7 @@ import kotlin.concurrent.thread | ||||||
| 
 | 
 | ||||||
| class SettingsFragment : PreferenceFragmentCompat() { | class SettingsFragment : PreferenceFragmentCompat() { | ||||||
|     companion object { |     companion object { | ||||||
|         private fun Context.getLayoutInt() : Int { |         private fun Context.getLayoutInt(): Int { | ||||||
|             val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) |             val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) | ||||||
|             return settingsManager.getInt(this.getString(R.string.app_layout_key), -1) |             return settingsManager.getInt(this.getString(R.string.app_layout_key), -1) | ||||||
|         } |         } | ||||||
|  | @ -70,11 +72,11 @@ class SettingsFragment : PreferenceFragmentCompat() { | ||||||
|             return value == 1 || value == 2 |             return value == 1 || value == 2 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         fun Context.isTrueTvSettings() : Boolean { |         fun Context.isTrueTvSettings(): Boolean { | ||||||
|             return getLayoutInt() == 1 |             return getLayoutInt() == 1 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         fun Context.isEmulatorSettings() : Boolean { |         fun Context.isEmulatorSettings(): Boolean { | ||||||
|             return getLayoutInt() == 2 |             return getLayoutInt() == 2 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -89,31 +91,32 @@ 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 = | ||||||
|         // It lies, it can be null if file manager quits. |         registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri -> | ||||||
|         if (uri == null) return@registerForActivityResult |             // It lies, it can be null if file manager quits. | ||||||
|         val context = context ?: AcraApplication.context ?: return@registerForActivityResult |             if (uri == null) return@registerForActivityResult | ||||||
|         // RW perms for the path |             val context = context ?: AcraApplication.context ?: return@registerForActivityResult | ||||||
|         val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or |             // RW perms for the path | ||||||
|                 Intent.FLAG_GRANT_WRITE_URI_PERMISSION |             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) |             val file = UniFile.fromUri(context, uri) | ||||||
|         println("Selected URI path: $uri - Full path: ${file.filePath}") |             println("Selected URI path: $uri - Full path: ${file.filePath}") | ||||||
| 
 | 
 | ||||||
|         // Stores the real URI using download_path_key |             // Stores the real URI using download_path_key | ||||||
|         // Important that the URI is stored instead of filepath due to permissions. |             // 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 { |  | ||||||
|             PreferenceManager.getDefaultSharedPreferences(context) |             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 |     // idk, if you find a way of automating this it would be great | ||||||
|     // https://www.iemoji.com/view/emoji/1794/flags/antarctica |     // https://www.iemoji.com/view/emoji/1794/flags/antarctica | ||||||
|  | @ -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,8 +341,9 @@ 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 = | ||||||
|                         File.separator + resources.getString(R.string.app_name_download_path) |                     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) |                 val first = listOf(defaultDir, secondaryDir) | ||||||
|                 (try { |                 (try { | ||||||
|                     val currentDir = context?.getBasePath()?.let { it.first?.filePath ?: it.second } |                     val currentDir = context?.getBasePath()?.let { it.first?.filePath ?: it.second } | ||||||
|  | @ -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"), | ||||||
|  | @ -377,21 +374,23 @@ class SettingsFragment : PreferenceFragmentCompat() { | ||||||
|                 if (it == dirs.size) { |                 if (it == dirs.size) { | ||||||
|                     try { |                     try { | ||||||
|                         pathPicker.launch(Uri.EMPTY) |                         pathPicker.launch(Uri.EMPTY) | ||||||
|                     } catch (e : Exception) { |                     } catch (e: Exception) { | ||||||
|                         logError(e) |                         logError(e) | ||||||
|                     } |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     // 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,28 +541,32 @@ 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 -> | ||||||
|  |                 pref.summary = | ||||||
|  |                     if (beneneCount <= 0) getString(R.string.benene_count_text_none) else getString( | ||||||
|  |                         R.string.benene_count_text | ||||||
|  |                     ).format( | ||||||
|  |                         beneneCount | ||||||
|  |                     ) | ||||||
| 
 | 
 | ||||||
|             benenePreference.summary = |                 pref.setOnPreferenceClickListener { | ||||||
|                 if (beneneCount <= 0) getString(R.string.benene_count_text_none) else getString(R.string.benene_count_text).format( |                     try { | ||||||
|                     beneneCount |                         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 { |                     return@setOnPreferenceClickListener true | ||||||
|                 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 |  | ||||||
|             } |             } | ||||||
|         } 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…
	
	Add table
		Add a link
		
	
		Reference in a new issue