This commit is contained in:
LagradOst 2022-04-08 21:38:19 +02:00
parent 8c5db9de11
commit f85d32a308
10 changed files with 142 additions and 34 deletions

View file

@ -12,7 +12,6 @@ import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.lagradost.cloudstream3.animeproviders.* import com.lagradost.cloudstream3.animeproviders.*
import com.lagradost.cloudstream3.metaproviders.CrossTmdbProvider import com.lagradost.cloudstream3.metaproviders.CrossTmdbProvider
import com.lagradost.cloudstream3.metaproviders.MultiAnimeProvider
import com.lagradost.cloudstream3.movieproviders.* import com.lagradost.cloudstream3.movieproviders.*
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.aniListApi import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.aniListApi
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi
@ -310,7 +309,7 @@ abstract class MainAPI {
var overrideData: HashMap<String, ProvidersInfoJson>? = null var overrideData: HashMap<String, ProvidersInfoJson>? = null
} }
public fun overrideWithNewData(data: ProvidersInfoJson) { fun overrideWithNewData(data: ProvidersInfoJson) {
this.name = data.name this.name = data.name
this.mainUrl = data.url this.mainUrl = data.url
} }
@ -760,6 +759,10 @@ interface LoadResponse {
// TODO add kitsu sync // TODO add kitsu sync
} }
fun LoadResponse.addTMDbId(id: String?) {
}
fun LoadResponse.setDuration(input: String?) { fun LoadResponse.setDuration(input: String?) {
val cleanInput = input?.trim()?.replace(" ", "") ?: return val cleanInput = input?.trim()?.replace(" ", "") ?: return
Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values -> Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values ->
@ -985,7 +988,7 @@ fun fetchUrls(text: String?): List<String> {
return listOf() return listOf()
} }
val linkRegex = val linkRegex =
Regex("""(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*))""") Regex("""(https?://(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*))""")
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList() return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
} }

View file

@ -26,6 +26,10 @@ fun <T, R> Iterable<T>.pmap(
return ArrayList<R>(destination) return ArrayList<R>(destination)
}*/ }*/
fun <K, V, R> Map<out K, V>.apmap(f: suspend (Map.Entry<K, V>) -> R): List<R> = runBlocking {
map { async { f(it) } }.map { it.await() }
}
fun <A, B> List<A>.apmap(f: suspend (A) -> B): List<B> = runBlocking { fun <A, B> List<A>.apmap(f: suspend (A) -> B): List<B> = runBlocking {
map { async { f(it) } }.map { it.await() } map { async { f(it) } }.map { it.await() }
} }

View file

@ -56,7 +56,10 @@ const val SKIP_OP_VIDEO_PERCENTAGE = 50
const val PRELOAD_NEXT_EPISODE_PERCENTAGE = 80 const val PRELOAD_NEXT_EPISODE_PERCENTAGE = 80
// when the player should mark the episode as watched and resume watching the next // when the player should mark the episode as watched and resume watching the next
const val NEXT_WATCH_EPISODE_PERCENTAGE = 95 const val NEXT_WATCH_EPISODE_PERCENTAGE = 90
// when the player should sync the progress of "watched", TODO MAKE SETTING
const val UPDATE_SYNC_PROGRESS_PERCENTAGE = 80
abstract class AbstractPlayerFragment( abstract class AbstractPlayerFragment(
val player: IPlayer = CS3IPlayer() val player: IPlayer = CS3IPlayer()
@ -329,6 +332,7 @@ abstract class AbstractPlayerFragment(
SKIP_OP_VIDEO_PERCENTAGE, SKIP_OP_VIDEO_PERCENTAGE,
PRELOAD_NEXT_EPISODE_PERCENTAGE, PRELOAD_NEXT_EPISODE_PERCENTAGE,
NEXT_WATCH_EPISODE_PERCENTAGE, NEXT_WATCH_EPISODE_PERCENTAGE,
UPDATE_SYNC_PROGRESS_PERCENTAGE,
), subtitlesUpdates = ::subtitlesChanged ), subtitlesUpdates = ::subtitlesChanged
) )

View file

@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.player
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -12,6 +13,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import com.google.android.exoplayer2.util.MimeTypes import com.google.android.exoplayer2.util.MimeTypes
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
@ -25,6 +27,7 @@ import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType
import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.ResultEpisode
import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.result.SyncViewModel
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
@ -39,13 +42,18 @@ import kotlinx.coroutines.Job
class GeneratorPlayer : FullScreenPlayer() { class GeneratorPlayer : FullScreenPlayer() {
companion object { companion object {
private var lastUsedGenerator: IGenerator? = null private var lastUsedGenerator: IGenerator? = null
fun newInstance(generator: IGenerator): Bundle { fun newInstance(generator: IGenerator, syncData: HashMap<String, String>? = null): Bundle {
Log.i(TAG, "newInstance = $syncData")
lastUsedGenerator = generator lastUsedGenerator = generator
return Bundle() return Bundle().apply {
if (syncData != null)
putSerializable("syncData", syncData)
}
} }
} }
private lateinit var viewModel: PlayerGeneratorViewModel //by activityViewModels() private lateinit var viewModel: PlayerGeneratorViewModel //by activityViewModels()
private lateinit var sync: SyncViewModel
private var currentLinks: Set<Pair<ExtractorLink?, ExtractorUri?>> = setOf() private var currentLinks: Set<Pair<ExtractorLink?, ExtractorUri?>> = setOf()
private var currentSubs: Set<SubtitleData> = setOf() private var currentSubs: Set<SubtitleData> = setOf()
@ -123,7 +131,11 @@ class GeneratorPlayer : FullScreenPlayer() {
if (isNextEpisode) 0L else getPos() if (isNextEpisode) 0L else getPos()
}, },
currentSubs, currentSubs,
(if(sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle(currentSubs, settings = true, downloads = true), (if (sameEpisode) currentSelectedSubtitles else null) ?: getAutoSelectSubtitle(
currentSubs,
settings = true,
downloads = true
),
) )
} }
} }
@ -422,6 +434,18 @@ class GeneratorPlayer : FullScreenPlayer() {
var isOpVisible = false var isOpVisible = false
when (val meta = currentMeta) { when (val meta = currentMeta) {
is ResultEpisode -> { is ResultEpisode -> {
if (percentage >= UPDATE_SYNC_PROGRESS_PERCENTAGE) {
context?.let { ctx ->
val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx)
if (settingsManager.getBoolean(
ctx.getString(R.string.episode_sync_enabled_key),
true
)
)
sync.modifyMaxEpisode(meta.episode)
}
}
if (meta.tvType.isAnimeOp()) if (meta.tvType.isAnimeOp())
isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE isOpVisible = percentage < SKIP_OP_VIDEO_PERCENTAGE
} }
@ -429,7 +453,7 @@ class GeneratorPlayer : FullScreenPlayer() {
player_skip_op?.isVisible = isOpVisible player_skip_op?.isVisible = isOpVisible
player_skip_episode?.isVisible = !isOpVisible && viewModel.hasNextEpisode() == true player_skip_episode?.isVisible = !isOpVisible && viewModel.hasNextEpisode() == true
if (percentage > PRELOAD_NEXT_EPISODE_PERCENTAGE) { if (percentage >= PRELOAD_NEXT_EPISODE_PERCENTAGE) {
viewModel.preLoadNextLinks() viewModel.preLoadNextLinks()
} }
} }
@ -554,6 +578,13 @@ class GeneratorPlayer : FullScreenPlayer() {
setPlayerDimen(widthHeight) setPlayerDimen(widthHeight)
} }
private fun unwrapBundle(savedInstanceState: Bundle?) {
Log.i(TAG, "unwrapBundle = $savedInstanceState")
savedInstanceState?.let { bundle ->
sync.addSyncs(bundle.getSerializable("syncData") as? HashMap<String, String>?)
}
}
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -564,13 +595,23 @@ class GeneratorPlayer : FullScreenPlayer() {
if (context?.isTvSettings() == true) R.layout.fragment_player_tv else R.layout.fragment_player if (context?.isTvSettings() == true) R.layout.fragment_player_tv else R.layout.fragment_player
viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java] viewModel = ViewModelProvider(this)[PlayerGeneratorViewModel::class.java]
sync = ViewModelProvider(this)[SyncViewModel::class.java]
viewModel.attachGenerator(lastUsedGenerator) viewModel.attachGenerator(lastUsedGenerator)
unwrapBundle(savedInstanceState)
unwrapBundle(arguments)
return super.onCreateView(inflater, container, savedInstanceState) return super.onCreateView(inflater, container, savedInstanceState)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
unwrapBundle(savedInstanceState)
unwrapBundle(arguments)
sync.updateUserData()
preferredAutoSelectSubtitles = SubtitlesFragment.getAutoSelectLanguageISO639_1() preferredAutoSelectSubtitles = SubtitlesFragment.getAutoSelectLanguageISO639_1()
if (currentSelectedLink == null) { if (currentSelectedLink == null) {

View file

@ -428,7 +428,8 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
private var currentHeaderName: String? = null private var currentHeaderName: String? = null
private var currentType: TvType? = null private var currentType: TvType? = null
private var currentEpisodes: List<ResultEpisode>? = null private var currentEpisodes: List<ResultEpisode>? = null
var downloadButton: EasyDownloadButton? = null private var downloadButton: EasyDownloadButton? = null
private var syncdata: Map<String, String>? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -645,6 +646,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
} }
private fun updateUI() { private fun updateUI() {
syncModel.updateUserData()
viewModel.reloadEpisodes() viewModel.reloadEpisodes()
} }
@ -1089,10 +1091,11 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
ACTION_PLAY_EPISODE_IN_PLAYER -> { ACTION_PLAY_EPISODE_IN_PLAYER -> {
viewModel.getGenerator(episodeClick.data) viewModel.getGenerator(episodeClick.data)
?.let { generator -> ?.let { generator ->
println("LANUCJ:::: $syncdata")
activity?.navigate( activity?.navigate(
R.id.global_to_navigation_player, R.id.global_to_navigation_player,
GeneratorPlayer.newInstance( GeneratorPlayer.newInstance(
generator generator, syncdata?.let { HashMap(it) }
) )
) )
} }
@ -1296,10 +1299,16 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
list.filter { it.isSynced && it.hasAccount }.joinToString { it.name } list.filter { it.isSynced && it.hasAccount }.joinToString { it.name }
val newList = list.filter { it.isSynced } val newList = list.filter { it.isSynced }
result_mini_sync?.isVisible = newList.isNotEmpty() result_mini_sync?.isVisible = newList.isNotEmpty()
(result_mini_sync?.adapter as? ImageAdapter?)?.updateList(newList.map { it.icon }) (result_mini_sync?.adapter as? ImageAdapter?)?.updateList(newList.map { it.icon })
} }
observe(syncModel.syncIds) {
println("VALUES::: $it")
syncdata = it
}
var currentSyncProgress = 0 var currentSyncProgress = 0
fun setSyncMaxEpisodes(totalEpisodes: Int?) { fun setSyncMaxEpisodes(totalEpisodes: Int?) {
@ -1642,12 +1651,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
setActors(d.actors) setActors(d.actors)
if (SettingsFragment.accountEnabled) { if (SettingsFragment.accountEnabled) {
var isValid = false if (syncModel.addSyncs(d.syncData)) {
for ((prefix, id) in d.syncData) {
isValid = isValid || syncModel.addSync(prefix, id)
}
if (isValid) {
syncModel.updateMetaAndUser() syncModel.updateMetaAndUser()
syncModel.updateSynced() syncModel.updateSynced()
} else { } else {

View file

@ -5,6 +5,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.SyncApis import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.SyncApis
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.aniListApi import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.aniListApi
@ -40,7 +41,10 @@ class SyncViewModel : ViewModel() {
val userData: LiveData<Resource<SyncAPI.SyncStatus>?> get() = _userDataResponse val userData: LiveData<Resource<SyncAPI.SyncStatus>?> get() = _userDataResponse
// prefix, id // prefix, id
private val syncIds = hashMapOf<String, String>() private var syncs = mutableMapOf<String, String>()
private val _syncIds: MutableLiveData<MutableMap<String, String>> =
MutableLiveData(mutableMapOf())
val syncIds: LiveData<MutableMap<String, String>> get() = _syncIds
private val _currentSynced: MutableLiveData<List<CurrentSynced>> = private val _currentSynced: MutableLiveData<List<CurrentSynced>> =
MutableLiveData(getMissing()) MutableLiveData(getMissing())
@ -53,7 +57,7 @@ class SyncViewModel : ViewModel() {
CurrentSynced( CurrentSynced(
it.name, it.name,
it.idPrefix, it.idPrefix,
syncIds.containsKey(it.idPrefix), syncs.containsKey(it.idPrefix),
it.hasAccount(), it.hasAccount(),
it.icon, it.icon,
) )
@ -65,13 +69,24 @@ class SyncViewModel : ViewModel() {
_currentSynced.postValue(getMissing()) _currentSynced.postValue(getMissing())
} }
fun addSync(idPrefix: String, id : String) : Boolean { private fun addSync(idPrefix: String, id: String): Boolean {
if(syncIds[idPrefix] == id) return false if (syncs[idPrefix] == id) return false
Log.i(TAG, "addSync $idPrefix = $id") Log.i(TAG, "addSync $idPrefix = $id")
syncIds[idPrefix] = id
syncs[idPrefix] = id
_syncIds.postValue(syncs)
return true return true
} }
fun addSyncs(map: Map<String, String>?): Boolean {
var isValid = false
map?.forEach { (prefix, id) ->
isValid = isValid || addSync(prefix, id)
}
return isValid
}
fun setMalId(id: String?): Boolean { fun setMalId(id: String?): Boolean {
return addSync(malApi.idPrefix, id ?: return false) return addSync(malApi.idPrefix, id ?: return false)
} }
@ -153,18 +168,43 @@ class SyncViewModel : ViewModel() {
Log.i(TAG, "publishUserData") Log.i(TAG, "publishUserData")
val user = userData.value val user = userData.value
if (user is Resource.Success) { if (user is Resource.Success) {
for ((prefix, id) in syncIds) { syncs.forEach { (prefix, id) ->
repos.firstOrNull { it.idPrefix == prefix }?.score(id, user.value) repos.firstOrNull { it.idPrefix == prefix }?.score(id, user.value)
} }
} }
updateUserData() updateUserData()
} }
private fun updateUserData() = viewModelScope.launch { fun modifyMaxEpisode(episodeNum: Int) {
Log.i(TAG, "modifyMaxEpisode = $episodeNum")
modifyData { status ->
status.copy(watchedEpisodes = maxOf(episodeNum, status.watchedEpisodes ?: return@modifyData null))
}
}
/// modifies the current sync data, return null if you don't want to change it
private fun modifyData(update: ((SyncAPI.SyncStatus) -> (SyncAPI.SyncStatus?))) =
viewModelScope.launch {
syncs.apmap { (prefix, id) ->
repos.firstOrNull { it.idPrefix == prefix }?.let { repo ->
if (repo.hasAccount()) {
val result = repo.getStatus(id)
if (result is Resource.Success) {
update(result.value)?.let { newData ->
Log.i(TAG, "modifyData ${repo.name} => $newData")
repo.score(id, newData)
}
}
}
}
}
}
fun updateUserData() = viewModelScope.launch {
Log.i(TAG, "updateUserData") Log.i(TAG, "updateUserData")
_userDataResponse.postValue(Resource.Loading()) _userDataResponse.postValue(Resource.Loading())
var lastError: Resource<SyncAPI.SyncStatus> = Resource.Failure(false, null, null, "No data") var lastError: Resource<SyncAPI.SyncStatus> = Resource.Failure(false, null, null, "No data")
for ((prefix, id) in syncIds) { syncs.forEach { (prefix, id) ->
repos.firstOrNull { it.idPrefix == prefix }?.let { repo -> repos.firstOrNull { it.idPrefix == prefix }?.let { repo ->
if (repo.hasAccount()) { if (repo.hasAccount()) {
val result = repo.getStatus(id) val result = repo.getStatus(id)
@ -185,7 +225,7 @@ class SyncViewModel : ViewModel() {
_metaResponse.postValue(Resource.Loading()) _metaResponse.postValue(Resource.Loading())
var lastError: Resource<SyncAPI.SyncResult> = Resource.Failure(false, null, null, "No data") var lastError: Resource<SyncAPI.SyncResult> = Resource.Failure(false, null, null, "No data")
for ((prefix, id) in syncIds) { syncs.forEach { (prefix, id) ->
repos.firstOrNull { it.idPrefix == prefix }?.let { repo -> repos.firstOrNull { it.idPrefix == prefix }?.let { repo ->
if (repo.hasAccount()) { if (repo.hasAccount()) {
val result = repo.getResult(id) val result = repo.getResult(id)

View file

@ -3,7 +3,7 @@
android:height="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24" android:viewportHeight="24"
android:tint="?attr/colorControlNormal"> android:tint="?attr/white">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z"/> android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z"/>

View file

@ -39,6 +39,7 @@
<string name="backup_key" translatable="false">backup_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>
<string name="episode_sync_enabled_key" translatable="false">episode_sync_enabled_key</string>
<!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG --> <!-- FORMAT MIGHT TRANSLATE, WILL CAUSE CRASH IF APPLIED WRONG -->
@ -202,6 +203,9 @@
overlay overlay
</string> </string>
<string name="episode_sync_settings">Update watch progress</string>
<string name="episode_sync_settings_des">Automatically sync your current episode progress</string>
<string name="restore_settings">Restore data from backup</string> <string name="restore_settings">Restore data from backup</string>
<string name="killswitch_settings">Download latest metadata from github</string> <string name="killswitch_settings">Download latest metadata from github</string>
<string name="killswitch_settings_des">If you want access to all providers (even broken ones) turn this off</string> <string name="killswitch_settings_des">If you want access to all providers (even broken ones) turn this off</string>

View file

@ -63,6 +63,14 @@
android:title="@string/double_tap_to_pause_settings" android:title="@string/double_tap_to_pause_settings"
android:summary="@string/double_tap_to_pause_settings_des" android:summary="@string/double_tap_to_pause_settings_des"
app:defaultValue="false" /> app:defaultValue="false" />
<SwitchPreference
android:icon="@drawable/baseline_sync_24"
app:key="@string/episode_sync_enabled_key"
android:title="@string/episode_sync_settings"
android:summary="@string/episode_sync_settings_des"
app:defaultValue="true" />
<Preference <Preference
android:key="@string/video_buffer_disk_key" android:key="@string/video_buffer_disk_key"
android:title="@string/video_buffer_disk_settings" android:title="@string/video_buffer_disk_settings"