Dynamic source settings

This commit is contained in:
Koitharu
2024-07-24 15:12:08 +03:00
parent 43c65bf95b
commit 10dc1d10ed
20 changed files with 109 additions and 64 deletions

View File

@@ -17,7 +17,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.ext.configureForParser
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
@@ -44,7 +44,7 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
}
val mangaSource = MangaSource(intent?.getStringExtra(EXTRA_SOURCE))
val repository = mangaRepositoryFactory.create(mangaSource) as? RemoteMangaRepository
val repository = mangaRepositoryFactory.create(mangaSource) as? ParserMangaRepository
repository?.headers?.get(CommonHeaders.USER_AGENT)
viewBinding.webView.configureForParser(userAgent)
CookieManager.getInstance().setAcceptThirdPartyCookies(viewBinding.webView, true)

View File

@@ -11,7 +11,7 @@ import okio.IOException
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mergeWith
@@ -30,7 +30,7 @@ class CommonHeadersInterceptor @Inject constructor(
val request = chain.request()
val source = request.tag(MangaSource::class.java)
val repository = if (source != null) {
mangaRepositoryFactoryLazy.get().create(source) as? RemoteMangaRepository
mangaRepositoryFactoryLazy.get().create(source) as? ParserMangaRepository
} else {
if (BuildConfig.DEBUG) {
Log.w("Http", "Request without source tag: ${request.url}")

View File

@@ -13,7 +13,7 @@ import okhttp3.internal.canParseAsIpAddress
import okhttp3.internal.closeQuietly
import okhttp3.internal.publicsuffix.PublicSuffixDatabase
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaSource
@@ -54,7 +54,7 @@ class MirrorSwitchInterceptor @Inject constructor(
}
}
suspend fun trySwitchMirror(repository: RemoteMangaRepository): Boolean = runInterruptible(Dispatchers.Default) {
suspend fun trySwitchMirror(repository: ParserMangaRepository): Boolean = runInterruptible(Dispatchers.Default) {
if (!isEnabled) {
return@runInterruptible false
}
@@ -76,14 +76,14 @@ class MirrorSwitchInterceptor @Inject constructor(
}
}
fun rollback(repository: RemoteMangaRepository, oldMirror: String) = synchronized(obtainLock(repository.source)) {
fun rollback(repository: ParserMangaRepository, oldMirror: String) = synchronized(obtainLock(repository.source)) {
blacklist[repository.source]?.remove(oldMirror)
repository.domain = oldMirror
}
private fun trySwitchMirror(request: Request, chain: Interceptor.Chain): Response? {
val source = request.tag(MangaSource::class.java) ?: return null
val repository = mangaRepositoryFactoryLazy.get().create(source) as? RemoteMangaRepository ?: return null
val repository = mangaRepositoryFactoryLazy.get().create(source) as? ParserMangaRepository ?: return null
val mirrors = repository.getAvailableMirrors()
if (mirrors.isEmpty()) {
return null
@@ -94,7 +94,7 @@ class MirrorSwitchInterceptor @Inject constructor(
}
private fun tryMirrors(
repository: RemoteMangaRepository,
repository: ParserMangaRepository,
mirrors: List<String>,
chain: Interceptor.Chain,
request: Request,

View File

@@ -52,7 +52,7 @@ class MangaLinkResolver @Inject constructor(
val host = uri.host ?: return null
val repo = sourcesRepository.allMangaSources.asSequence()
.map { source ->
repositoryFactory.create(source) as RemoteMangaRepository
repositoryFactory.create(source) as ParserMangaRepository
}.find { repo ->
host in repo.domains
} ?: return null
@@ -86,7 +86,7 @@ class MangaLinkResolver @Inject constructor(
}
private suspend fun MangaRepository.getDetailsNoCache(manga: Manga): Manga {
return if (this is RemoteMangaRepository) {
return if (this is ParserMangaRepository) {
getDetails(manga, CachePolicy.READ_ONLY)
} else {
getDetails(manga)

View File

@@ -88,7 +88,7 @@ interface MangaRepository {
}
private fun createRepository(source: MangaSource): MangaRepository? = when (source) {
is MangaParserSource -> RemoteMangaRepository(
is MangaParserSource -> ParserMangaRepository(
parser = MangaParser(source, loaderContext),
cache = contentCache,
mirrorSwitchInterceptor = mirrorSwitchInterceptor,

View File

@@ -36,7 +36,7 @@ import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import java.util.Locale
class RemoteMangaRepository(
class ParserMangaRepository(
private val parser: MangaParser,
private val cache: MemoryContentCache,
private val mirrorSwitchInterceptor: MirrorSwitchInterceptor,
@@ -220,14 +220,14 @@ class RemoteMangaRepository(
if (result.isValidResult()) {
return result.getOrThrow()
}
return if (trySwitchMirror(this@RemoteMangaRepository)) {
return if (trySwitchMirror(this@ParserMangaRepository)) {
val newResult = runCatchingCancellable {
block()
}
if (newResult.isValidResult()) {
return newResult.getOrThrow()
} else {
rollback(this@RemoteMangaRepository, initialMirror)
rollback(this@ParserMangaRepository, initialMirror)
return result.getOrThrow()
}
} else {

View File

@@ -25,7 +25,7 @@ import okio.buffer
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.util.ext.requireBody
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
import org.koitharu.kotatsu.local.data.CacheDir
@@ -53,7 +53,7 @@ class FaviconFetcher(
override suspend fun fetch(): FetchResult {
getCached(options)?.let { return it }
val repo = mangaRepositoryFactory.create(mangaSource) as RemoteMangaRepository
val repo = mangaRepositoryFactory.create(mangaSource) as ParserMangaRepository
val sizePx = maxOf(
options.size.width.pxOrElse { FALLBACK_SIZE },
options.size.height.pxOrElse { FALLBACK_SIZE },

View File

@@ -25,7 +25,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.formatNumber
import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.ui.model.DateTimeAgo
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
@@ -326,6 +326,6 @@ class DownloadsViewModel @Inject constructor(
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
private suspend fun tryLoad(manga: Manga) = runCatchingCancellable {
(mangaRepositoryFactory.create(manga.source) as RemoteMangaRepository).getDetails(manga)
(mangaRepositoryFactory.create(manga.source) as ParserMangaRepository).getDetails(manga)
}.getOrNull()
}

View File

@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.download.ui.worker
import androidx.collection.MutableObjectLongMap
import kotlinx.coroutines.delay
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.parsers.model.MangaSource
class DownloadSlowdownDispatcher(
@@ -13,7 +13,7 @@ class DownloadSlowdownDispatcher(
private val timeMap = MutableObjectLongMap<MangaSource>()
suspend fun delay(source: MangaSource) {
val repo = mangaRepositoryFactory.create(source) as? RemoteMangaRepository ?: return
val repo = mangaRepositoryFactory.create(source) as? ParserMangaRepository ?: return
if (!repo.isSlowdownEnabled()) {
return
}

View File

@@ -12,7 +12,7 @@ import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
import org.koitharu.kotatsu.core.model.findById
import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.parsers.exception.ParseException
@@ -73,7 +73,7 @@ class CoverRestoreInterceptor @Inject constructor(
if (dataRepository.findMangaById(manga.id) == null) {
return false
}
val repo = repositoryFactory.create(manga.source) as? RemoteMangaRepository ?: return false
val repo = repositoryFactory.create(manga.source) as? ParserMangaRepository ?: return false
val fixed = repo.find(manga) ?: return false
return if (fixed != manga) {
dataRepository.storeManga(fixed)
@@ -100,7 +100,7 @@ class CoverRestoreInterceptor @Inject constructor(
}
private suspend fun restoreBookmarkImpl(bookmark: Bookmark): Boolean {
val repo = repositoryFactory.create(bookmark.manga.source) as? RemoteMangaRepository ?: return false
val repo = repositoryFactory.create(bookmark.manga.source) as? ParserMangaRepository ?: return false
val chapter = repo.getDetails(bookmark.manga).chapters?.findById(bookmark.chapterId) ?: return false
val page = repo.getPages(chapter)[bookmark.page]
val imageUrl = page.preview.ifNullOrEmpty { page.url }

View File

@@ -32,7 +32,7 @@ import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
@@ -91,7 +91,7 @@ class PageLoader @Inject constructor(
private val edgeDetector = EdgeDetector(context)
fun isPrefetchApplicable(): Boolean {
return repository is RemoteMangaRepository
return repository is ParserMangaRepository
&& settings.isPagesPreloadEnabled
&& !context.isPowerSaveMode()
&& !isLowRam()

View File

@@ -7,7 +7,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.util.ext.mapToArray
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.MangaSource
@@ -20,7 +20,7 @@ class ImageServerDelegate(
) {
private val repositoryLazy = SuspendLazy {
mangaRepositoryFactory.create(checkNotNull(mangaSource)) as RemoteMangaRepository
mangaRepositoryFactory.create(checkNotNull(mangaSource)) as ParserMangaRepository
}
suspend fun isAvailable() = withContext(Dispatchers.Default) {

View File

@@ -21,7 +21,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.distinctById
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
@@ -75,7 +75,7 @@ open class RemoteListViewModel @Inject constructor(
get() = repository.isSearchSupported
val browserUrl: String?
get() = (repository as? RemoteMangaRepository)?.domain?.let { "https://$it" }
get() = (repository as? ParserMangaRepository)?.domain?.let { "https://$it" }
override val content = combine(
mangaList.map { it?.skipNsfwIfNeeded() },

View File

@@ -7,7 +7,9 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.EmptyMangaRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.util.ext.mapToArray
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.network.UserAgents
@@ -17,7 +19,14 @@ import org.koitharu.kotatsu.settings.utils.EditTextDefaultSummaryProvider
import org.koitharu.kotatsu.settings.utils.validation.DomainValidator
import org.koitharu.kotatsu.settings.utils.validation.HeaderValidator
fun PreferenceFragmentCompat.addPreferencesFromRepository(repository: RemoteMangaRepository) {
fun PreferenceFragmentCompat.addPreferencesFromRepository(repository: MangaRepository) = when (repository) {
is ParserMangaRepository -> addPreferencesFromParserRepository(repository)
is EmptyMangaRepository -> addPreferencesFromEmptyRepository()
else -> Unit
}
private fun PreferenceFragmentCompat.addPreferencesFromParserRepository(repository: ParserMangaRepository) {
addPreferencesFromResource(R.xml.pref_source_parser)
val configKeys = repository.getConfigKeys()
val screen = preferenceScreen
for (key in configKeys) {
@@ -100,6 +109,16 @@ fun PreferenceFragmentCompat.addPreferencesFromRepository(repository: RemoteMang
}
}
private fun PreferenceFragmentCompat.addPreferencesFromEmptyRepository() {
val preference = Preference(requireContext())
preference.setIcon(R.drawable.ic_alert_outline)
preference.isPersistent = false
preference.isSelectable = false
preference.order = 200
preference.setSummary(R.string.unsupported_source)
preferenceScreen.addPreference(preference)
}
private fun Array<out String>.toStringArray(): Array<String> {
return Array(size) { i -> this[i] as? String ?: "" }
}

View File

@@ -10,7 +10,10 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
import org.koitharu.kotatsu.core.model.getTitle
import org.koitharu.kotatsu.core.parser.EmptyMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
import org.koitharu.kotatsu.core.util.ext.observe
@@ -37,15 +40,18 @@ class SourceSettingsFragment : BasePreferenceFragment(0), Preference.OnPreferenc
preferenceManager.sharedPreferencesName = viewModel.source.name
addPreferencesFromResource(R.xml.pref_source)
addPreferencesFromRepository(viewModel.repository)
val isValidSource = viewModel.repository !is EmptyMangaRepository
findPreference<SwitchPreferenceCompat>(KEY_ENABLE)?.run {
isVisible = isValidSource
onPreferenceChangeListener = this@SourceSettingsFragment
}
findPreference<Preference>(KEY_AUTH)?.run {
val authProvider = viewModel.repository.getAuthProvider()
val authProvider = (viewModel.repository as? ParserMangaRepository)?.getAuthProvider()
isVisible = authProvider != null
isEnabled = authProvider?.isAuthorized == false
}
findPreference<Preference>(SourceSettings.KEY_SLOWDOWN)?.isVisible = isValidSource
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@@ -11,13 +11,14 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import javax.inject.Inject
@@ -30,7 +31,7 @@ class SourceSettingsViewModel @Inject constructor(
) : BaseViewModel(), SharedPreferences.OnSharedPreferenceChangeListener {
val source = MangaSource(savedStateHandle.get<String>(SourceSettingsFragment.EXTRA_SOURCE))
val repository = mangaRepositoryFactory.create(source) as RemoteMangaRepository
val repository = mangaRepositoryFactory.create(source)
val onActionDone = MutableEventFlow<ReversibleAction>()
val username = MutableStateFlow<String?>(null)
@@ -38,28 +39,41 @@ class SourceSettingsViewModel @Inject constructor(
private var usernameLoadJob: Job? = null
init {
repository.getConfig().subscribe(this)
loadUsername()
when (repository) {
is ParserMangaRepository -> {
repository.getConfig().subscribe(this)
loadUsername(repository.getAuthProvider())
}
}
}
override fun onCleared() {
repository.getConfig().unsubscribe(this)
when (repository) {
is ParserMangaRepository -> {
repository.getConfig().unsubscribe(this)
}
}
super.onCleared()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key != SourceSettings.KEY_SLOWDOWN && key != SourceSettings.KEY_SORT_ORDER) {
repository.invalidateCache()
when (repository) {
is ParserMangaRepository -> {
if (key != SourceSettings.KEY_SLOWDOWN && key != SourceSettings.KEY_SORT_ORDER) {
repository.invalidateCache()
}
}
}
}
fun onResume() {
if (usernameLoadJob?.isActive != true) {
loadUsername()
if (usernameLoadJob?.isActive != true && repository is ParserMangaRepository) {
loadUsername(repository.getAuthProvider())
}
}
fun clearCookies() {
if (repository !is ParserMangaRepository) return
launchLoadingJob(Dispatchers.Default) {
val url = HttpUrl.Builder()
.scheme("https")
@@ -67,7 +81,7 @@ class SourceSettingsViewModel @Inject constructor(
.build()
cookieJar.removeCookies(url, null)
onActionDone.call(ReversibleAction(R.string.cookies_cleared, null))
loadUsername()
loadUsername(repository.getAuthProvider())
}
}
@@ -77,11 +91,11 @@ class SourceSettingsViewModel @Inject constructor(
}
}
private fun loadUsername() {
private fun loadUsername(authProvider: MangaParserAuthProvider?) {
launchLoadingJob(Dispatchers.Default) {
try {
username.value = null
username.value = repository.getAuthProvider()?.getUsername()
username.value = authProvider?.getUsername()
} catch (_: AuthRequiredException) {
}
}

View File

@@ -21,7 +21,7 @@ import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.TaggedActivityResult
import org.koitharu.kotatsu.core.util.ext.configureForParser
@@ -52,7 +52,7 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
finishAfterTransition()
return
}
val repository = mangaRepositoryFactory.create(source) as? RemoteMangaRepository
val repository = mangaRepositoryFactory.create(source) as? ParserMangaRepository
authProvider = (repository)?.getAuthProvider() ?: run {
Toast.makeText(
this,

View File

@@ -5,7 +5,7 @@ import coil.request.CachePolicy
import dagger.Reusable
import org.koitharu.kotatsu.core.model.getPreferredBranch
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.util.MultiMutex
import org.koitharu.kotatsu.core.util.ext.toInstantOrNull
import org.koitharu.kotatsu.history.data.HistoryRepository
@@ -36,7 +36,7 @@ class Tracker @Inject constructor(
): MangaUpdates = mangaMutex.withLock(track.manga.id) {
val updates = runCatchingCancellable {
val repo = mangaRepositoryFactory.create(track.manga.source)
require(repo is RemoteMangaRepository) { "Repository ${repo.javaClass.simpleName} is not supported" }
require(repo is ParserMangaRepository) { "Repository ${repo.javaClass.simpleName} is not supported" }
val manga = repo.getDetails(track.manga, CachePolicy.WRITE_ONLY)
compare(track, manga, getBranch(manga))
}.getOrElse { error ->

View File

@@ -11,21 +11,6 @@
android:persistent="false"
android:title="@string/enable_source" />
<Preference
android:key="auth"
android:order="100"
android:persistent="false"
android:title="@string/sign_in"
app:allowDividerAbove="true" />
<Preference
android:key="cookies_clear"
android:order="101"
android:persistent="false"
android:summary="@string/clear_source_cookies_summary"
android:title="@string/clear_cookies"
app:allowDividerAbove="true" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="slowdown"

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Preference
android:key="auth"
android:order="100"
android:persistent="false"
android:title="@string/sign_in"
app:allowDividerAbove="true" />
<Preference
android:key="cookies_clear"
android:order="101"
android:persistent="false"
android:summary="@string/clear_source_cookies_summary"
android:title="@string/clear_cookies"
app:allowDividerAbove="true" />
</PreferenceScreen>