Fixes and refactor

This commit is contained in:
Koitharu
2021-01-17 18:30:14 +02:00
parent a242aa6633
commit 8f8d85d172
41 changed files with 119 additions and 127 deletions

2
.idea/compiler.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" /> <bytecodeTargetLevel target="11" />
</component> </component>
</project> </project>

3
.idea/gradle.xml generated
View File

@@ -4,7 +4,7 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="PLATFORM" /> <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" /> <option name="gradleJvm" value="1.8" />
@@ -15,7 +15,6 @@
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

View File

@@ -2,6 +2,8 @@
<profile version="1.0"> <profile version="1.0">
<option name="myName" value="Project Default" /> <option name="myName" value="Project Default" />
<inspection_tool class="BooleanLiteralArgument" enabled="true" level="WEAK WARNING" enabled_by_default="true" /> <inspection_tool class="BooleanLiteralArgument" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="KeySetIterationMayUseEntrySet" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TrailingComma" enabled="true" level="INFORMATION" enabled_by_default="true" /> <inspection_tool class="TrailingComma" enabled="true" level="INFORMATION" enabled_by_default="true" />
<inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
</profile> </profile>
</component> </component>

2
.idea/misc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@@ -66,7 +66,7 @@ dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2'
implementation 'androidx.core:core-ktx:1.5.0-alpha05' implementation 'androidx.core:core-ktx:1.5.0-beta01'
implementation 'androidx.activity:activity-ktx:1.2.0-rc01' implementation 'androidx.activity:activity-ktx:1.2.0-rc01'
implementation 'androidx.fragment:fragment-ktx:1.3.0-rc01' implementation 'androidx.fragment:fragment-ktx:1.3.0-rc01'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-rc01' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-rc01'
@@ -79,8 +79,8 @@ dependencies {
implementation 'androidx.recyclerview:recyclerview:1.2.0-beta01' implementation 'androidx.recyclerview:recyclerview:1.2.0-beta01'
implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01' implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'androidx.work:work-runtime-ktx:2.5.0-beta02' implementation 'androidx.work:work-runtime-ktx:2.5.0-rc01'
implementation 'com.google.android.material:material:1.3.0-beta01' implementation 'com.google.android.material:material:1.3.0-rc01'
//noinspection LifecycleAnnotationProcessorWithJava8 //noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.3.0-rc01' kapt 'androidx.lifecycle:lifecycle-compiler:2.3.0-rc01'
@@ -89,7 +89,7 @@ dependencies {
kapt 'androidx.room:room-compiler:2.2.6' kapt 'androidx.room:room-compiler:2.2.6'
implementation 'com.squareup.okhttp3:okhttp:4.9.0' implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.squareup.okio:okio:2.9.0' implementation 'com.squareup.okio:okio:2.10.0'
implementation 'org.jsoup:jsoup:1.13.1' implementation 'org.jsoup:jsoup:1.13.1'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.0' implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.0'
@@ -97,7 +97,7 @@ dependencies {
implementation 'org.koin:koin-android:2.2.2' implementation 'org.koin:koin-android:2.2.2'
implementation 'org.koin:koin-androidx-viewmodel:2.2.2' implementation 'org.koin:koin-androidx-viewmodel:2.2.2'
implementation 'io.coil-kt:coil-base:1.1.0' implementation 'io.coil-kt:coil-base:1.1.1'
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
implementation 'com.tomclaw.cache:cache:1.0' implementation 'com.tomclaw.cache:cache:1.0'

View File

@@ -9,7 +9,6 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import org.koitharu.kotatsu.utils.ext.safe
class BrowserClient(private val callback: BrowserCallback) : WebViewClient(), KoinComponent { class BrowserClient(private val callback: BrowserCallback) : WebViewClient(), KoinComponent {
@@ -45,7 +44,7 @@ class BrowserClient(private val callback: BrowserCallback) : WebViewClient(), Ko
return request?.url?.toString()?.let(::doRequest) return request?.url?.toString()?.let(::doRequest)
} }
private fun doRequest(url: String): WebResourceResponse? = safe { private fun doRequest(url: String): WebResourceResponse? = runCatching {
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
.build() .build()
@@ -56,5 +55,5 @@ class BrowserClient(private val callback: BrowserCallback) : WebViewClient(), Ko
ct?.charset()?.name() ?: "utf-8", ct?.charset()?.name() ?: "utf-8",
response.body?.byteStream() response.body?.byteStream()
) )
} }.getOrNull()
} }

View File

@@ -4,7 +4,9 @@ import okhttp3.CookieJar
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.core.qualifier.named import org.koin.core.qualifier.named
import org.koin.dsl.bind
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.core.network.cookies.ClearableCookieJar
import org.koitharu.kotatsu.core.network.cookies.PersistentCookieJar import org.koitharu.kotatsu.core.network.cookies.PersistentCookieJar
import org.koitharu.kotatsu.core.network.cookies.cache.SetCookieCache import org.koitharu.kotatsu.core.network.cookies.cache.SetCookieCache
import org.koitharu.kotatsu.core.network.cookies.persistence.SharedPrefsCookiePersistor import org.koitharu.kotatsu.core.network.cookies.persistence.SharedPrefsCookiePersistor
@@ -18,7 +20,7 @@ val networkModule
SetCookieCache(), SetCookieCache(),
SharedPrefsCookiePersistor(androidContext()) SharedPrefsCookiePersistor(androidContext())
) )
} } bind ClearableCookieJar::class
single(named(CacheUtils.QUALIFIER_HTTP)) { CacheUtils.createHttpCache(androidContext()) } single(named(CacheUtils.QUALIFIER_HTTP)) { CacheUtils.createHttpCache(androidContext()) }
single { single {
OkHttpClient.Builder().apply { OkHttpClient.Builder().apply {

View File

@@ -2,16 +2,22 @@ package org.koitharu.kotatsu.core.network
import android.os.Build import android.os.Build
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import java.util.* import java.util.*
class UserAgentInterceptor : Interceptor { class UserAgentInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain) = chain.proceed( override fun intercept(chain: Interceptor.Chain): Response {
chain.request().newBuilder() val request = chain.request()
.header(HEADER_USER_AGENT, userAgent) return chain.proceed(
.build() if (request.header(HEADER_USER_AGENT) == null) {
) request.newBuilder()
.header(HEADER_USER_AGENT, userAgent)
.build()
} else request
)
}
companion object { companion object {

View File

@@ -35,7 +35,7 @@ class PersistentCookieJar(
cache.addAll(persistor.loadAll()) cache.addAll(persistor.loadAll())
} }
cache.addAll(cookies) cache.addAll(cookies)
persistor.saveAll(filterPersistentCookies(cookies)) persistor.saveAll(cookies.filter { it.persistent })
} }
@Synchronized @Synchronized
@@ -48,11 +48,14 @@ class PersistentCookieJar(
val it = cache.iterator() val it = cache.iterator()
while (it.hasNext()) { while (it.hasNext()) {
val currentCookie = it.next() val currentCookie = it.next()
if (isCookieExpired(currentCookie)) { when {
cookiesToRemove.add(currentCookie) currentCookie.isExpired() -> {
it.remove() cookiesToRemove.add(currentCookie)
} else if (currentCookie.matches(url)) { it.remove()
validCookies.add(currentCookie) }
currentCookie.matches(url) -> {
validCookies.add(currentCookie)
}
} }
} }
persistor.removeAll(cookiesToRemove) persistor.removeAll(cookiesToRemove)
@@ -73,18 +76,8 @@ class PersistentCookieJar(
private companion object { private companion object {
fun filterPersistentCookies(cookies: List<Cookie>): List<Cookie> { fun Cookie.isExpired(): Boolean {
val persistentCookies: MutableList<Cookie> = ArrayList() return expiresAt < System.currentTimeMillis()
for (cookie in cookies) {
if (cookie.persistent) {
persistentCookies.add(cookie)
}
}
return persistentCookies
}
fun isCookieExpired(cookie: Cookie): Boolean {
return cookie.expiresAt < System.currentTimeMillis()
} }
} }
} }

View File

@@ -19,7 +19,7 @@ val parserModule
factory<MangaRepository>(named(MangaSource.HENCHAN)) { HenChanRepository(get()) } factory<MangaRepository>(named(MangaSource.HENCHAN)) { HenChanRepository(get()) }
factory<MangaRepository>(named(MangaSource.YAOICHAN)) { YaoiChanRepository(get()) } factory<MangaRepository>(named(MangaSource.YAOICHAN)) { YaoiChanRepository(get()) }
factory<MangaRepository>(named(MangaSource.MANGATOWN)) { MangaTownRepository(get()) } factory<MangaRepository>(named(MangaSource.MANGATOWN)) { MangaTownRepository(get()) }
factory<MangaRepository>(named(MangaSource.MANGALIB)) { MangaLibRepository(get()) } single<MangaRepository>(named(MangaSource.MANGALIB)) { MangaLibRepository(get()) }
factory<MangaRepository>(named(MangaSource.NUDEMOON)) { NudeMoonRepository(get()) } factory<MangaRepository>(named(MangaSource.NUDEMOON)) { NudeMoonRepository(get()) }
factory<MangaRepository>(named(MangaSource.MANGAREAD)) { MangareadRepository(get()) } factory<MangaRepository>(named(MangaSource.MANGAREAD)) { MangareadRepository(get()) }
} }

View File

@@ -56,7 +56,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
).firstOrNull()?.text(), ).firstOrNull()?.text(),
coverUrl = row.selectFirst("div.manga_images")?.selectFirst("img") coverUrl = row.selectFirst("div.manga_images")?.selectFirst("img")
?.attr("src")?.withDomain(domain).orEmpty(), ?.attr("src")?.withDomain(domain).orEmpty(),
tags = safe { tags = runCatching {
row.selectFirst("div.genre")?.select("a")?.mapToSet { row.selectFirst("div.genre")?.select("a")?.mapToSet {
MangaTag( MangaTag(
title = it.text(), title = it.text(),
@@ -64,7 +64,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
source = source source = source
) )
} }
}.orEmpty(), }.getOrNull().orEmpty(),
source = source source = source
) )
} }

View File

@@ -1,7 +1,7 @@
package org.koitharu.kotatsu.core.parser.site package org.koitharu.kotatsu.core.parser.site
import androidx.collection.arraySetOf import androidx.collection.arraySetOf
import androidx.core.net.toUri import okhttp3.HttpUrl.Companion.toHttpUrl
import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.core.model.*
@@ -53,7 +53,7 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
}.parseHtml() }.parseHtml()
val root = doc.body().getElementById("mangaBox") val root = doc.body().getElementById("mangaBox")
?.selectFirst("div.tiles.row") ?: throw ParseException("Cannot find root") ?.selectFirst("div.tiles.row") ?: throw ParseException("Cannot find root")
val baseHost = root.baseUri().toUri().host val baseHost = root.baseUri().toHttpUrl().host
return root.select("div.tile").mapNotNull { node -> return root.select("div.tile").mapNotNull { node ->
val imgDiv = node.selectFirst("div.img") ?: return@mapNotNull null val imgDiv = node.selectFirst("div.img") ?: return@mapNotNull null
val descDiv = node.selectFirst("div.desc") ?: return@mapNotNull null val descDiv = node.selectFirst("div.desc") ?: return@mapNotNull null
@@ -61,7 +61,7 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
return@mapNotNull null //skip author return@mapNotNull null //skip author
} }
val href = imgDiv.selectFirst("a").attr("href")?.inContextOf(node) val href = imgDiv.selectFirst("a").attr("href")?.inContextOf(node)
if (href == null || href.toUri().host != baseHost) { if (href == null || href.toHttpUrl().host != baseHost) {
return@mapNotNull null // skip external links return@mapNotNull null // skip external links
} }
val title = descDiv.selectFirst("h3")?.selectFirst("a")?.text() val title = descDiv.selectFirst("h3")?.selectFirst("a")?.text()
@@ -73,15 +73,15 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
title = title, title = title,
altTitle = descDiv.selectFirst("h4")?.text(), altTitle = descDiv.selectFirst("h4")?.text(),
coverUrl = imgDiv.selectFirst("img.lazy")?.attr("data-original").orEmpty(), coverUrl = imgDiv.selectFirst("img.lazy")?.attr("data-original").orEmpty(),
rating = safe { rating = runCatching {
node.selectFirst("div.rating") node.selectFirst("div.rating")
?.attr("title") ?.attr("title")
?.substringBefore(' ') ?.substringBefore(' ')
?.toFloatOrNull() ?.toFloatOrNull()
?.div(10f) ?.div(10f)
} ?: Manga.NO_RATING, }.getOrNull() ?: Manga.NO_RATING,
author = tileInfo?.selectFirst("a.person-link")?.text(), author = tileInfo?.selectFirst("a.person-link")?.text(),
tags = safe { tags = runCatching {
tileInfo?.select("a.element-link") tileInfo?.select("a.element-link")
?.mapToSet { ?.mapToSet {
MangaTag( MangaTag(
@@ -90,7 +90,7 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
source = source source = source
) )
} }
}.orEmpty(), }.getOrNull().orEmpty(),
state = when { state = when {
node.selectFirst("div.tags") node.selectFirst("div.tags")
?.selectFirst("span.mangaCompleted") != null -> MangaState.FINISHED ?.selectFirst("span.mangaCompleted") != null -> MangaState.FINISHED

View File

@@ -80,14 +80,14 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
val title = root.selectFirst("div.media-header__wrap")?.children() val title = root.selectFirst("div.media-header__wrap")?.children()
val info = root.selectFirst("div.media-content") val info = root.selectFirst("div.media-content")
val chaptersDoc = loaderContext.httpGet(manga.url + "?section=chapters").parseHtml() val chaptersDoc = loaderContext.httpGet(manga.url + "?section=chapters").parseHtml()
val scripts = chaptersDoc.body().select("script") val scripts = chaptersDoc.select("script")
var chapters: ArrayList<MangaChapter>? = null var chapters: ArrayList<MangaChapter>? = null
scripts@ for (script in scripts) { scripts@ for (script in scripts) {
val raw = script.html().lines() val raw = script.html().lines()
for (line in raw) { for (line in raw) {
if (line.startsWith("window.__CHAPTERS_DATA__")) { if (line.startsWith("window.__DATA__")) {
val json = JSONObject(line.substringAfter('=').substringBeforeLast(';')) val json = JSONObject(line.substringAfter('=').substringBeforeLast(';'))
val list = json.getJSONArray("list") val list = json.getJSONObject("chapters").getJSONArray("list")
val total = list.length() val total = list.length()
chapters = ArrayList(total) chapters = ArrayList(total)
for (i in 0 until total) { for (i in 0 until total) {
@@ -99,7 +99,7 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
append("/c") append("/c")
append(item.getString("chapter_number")) append(item.getString("chapter_number"))
append('/') append('/')
append(item.getJSONArray("teams").getJSONObject(0).optString("slug")) append(item.optString("chapter_string"))
} }
var name = item.getString("chapter_name") var name = item.getString("chapter_name")
if (name.isNullOrBlank() || name == "null") { if (name.isNullOrBlank() || name == "null") {

View File

@@ -149,6 +149,7 @@ class AppSettings private constructor(private val prefs: SharedPreferences) :
const val KEY_SOURCES_HIDDEN = "sources_hidden" const val KEY_SOURCES_HIDDEN = "sources_hidden"
const val KEY_TRAFFIC_WARNING = "traffic_warning" const val KEY_TRAFFIC_WARNING = "traffic_warning"
const val KEY_PAGES_CACHE_CLEAR = "pages_cache_clear" const val KEY_PAGES_CACHE_CLEAR = "pages_cache_clear"
const val KEY_COOKIES_CLEAR = "cookies_clear"
const val KEY_THUMBS_CACHE_CLEAR = "thumbs_cache_clear" const val KEY_THUMBS_CACHE_CLEAR = "thumbs_cache_clear"
const val KEY_SEARCH_HISTORY_CLEAR = "search_history_clear" const val KEY_SEARCH_HISTORY_CLEAR = "search_history_clear"
const val KEY_UPDATES_FEED_CLEAR = "updates_feed_clear" const val KEY_UPDATES_FEED_CLEAR = "updates_feed_clear"

View File

@@ -17,7 +17,6 @@ import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.safe
import java.io.IOException import java.io.IOException
class DetailsViewModel( class DetailsViewModel(
@@ -93,7 +92,7 @@ class DetailsViewModel(
launchLoadingJob(Dispatchers.Default) { launchLoadingJob(Dispatchers.Default) {
val original = localMangaRepository.getRemoteManga(manga) val original = localMangaRepository.getRemoteManga(manga)
localMangaRepository.delete(manga) || throw IOException("Unable to delete file") localMangaRepository.delete(manga) || throw IOException("Unable to delete file")
safe { runCatching {
historyRepository.deleteOrSwap(manga, original) historyRepository.deleteOrSwap(manga, original)
} }
onMangaRemoved.postCall(manga) onMangaRemoved.postCall(manga)

View File

@@ -92,13 +92,13 @@ class DownloadService : BaseService() {
var output: MangaZip? = null var output: MangaZip? = null
try { try {
val repo = manga.source.repository val repo = manga.source.repository
val cover = safe { val cover = runCatching {
imageLoader.execute( imageLoader.execute(
ImageRequest.Builder(this@DownloadService) ImageRequest.Builder(this@DownloadService)
.data(manga.coverUrl) .data(manga.coverUrl)
.build() .build()
).drawable ).drawable
} }.getOrNull()
notification.setLargeIcon(cover) notification.setLargeIcon(cover)
notification.update() notification.update()
val data = if (manga.chapters == null) repo.getDetails(manga) else manga val data = if (manga.chapters == null) repo.getDetails(manga) else manga

View File

@@ -9,7 +9,6 @@ import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.utils.ext.getStringOrNull import org.koitharu.kotatsu.utils.ext.getStringOrNull
import org.koitharu.kotatsu.utils.ext.mapToSet import org.koitharu.kotatsu.utils.ext.mapToSet
import org.koitharu.kotatsu.utils.ext.safe
class MangaIndex(source: String?) { class MangaIndex(source: String?) {
@@ -40,7 +39,7 @@ class MangaIndex(source: String?) {
json.put("app_version", BuildConfig.VERSION_CODE) json.put("app_version", BuildConfig.VERSION_CODE)
} }
fun getMangaInfo(): Manga? = if (json.length() == 0) null else safe { fun getMangaInfo(): Manga? = if (json.length() == 0) null else runCatching {
val source = MangaSource.valueOf(json.getString("source")) val source = MangaSource.valueOf(json.getString("source"))
Manga( Manga(
id = json.getLong("id"), id = json.getLong("id"),
@@ -60,7 +59,7 @@ class MangaIndex(source: String?) {
}, },
chapters = getChapters(json.getJSONObject("chapters"), source) chapters = getChapters(json.getJSONObject("chapters"), source)
) )
} }.getOrNull()
fun getCoverEntry(): String? = json.optString("cover_entry") fun getCoverEntry(): String? = json.optString("cover_entry")

View File

@@ -15,7 +15,6 @@ import org.koitharu.kotatsu.local.data.MangaZip
import org.koitharu.kotatsu.utils.AlphanumComparator import org.koitharu.kotatsu.utils.AlphanumComparator
import org.koitharu.kotatsu.utils.ext.longHashCode import org.koitharu.kotatsu.utils.ext.longHashCode
import org.koitharu.kotatsu.utils.ext.readText import org.koitharu.kotatsu.utils.ext.readText
import org.koitharu.kotatsu.utils.ext.safe
import org.koitharu.kotatsu.utils.ext.sub import org.koitharu.kotatsu.utils.ext.sub
import java.io.File import java.io.File
import java.util.* import java.util.*
@@ -37,7 +36,7 @@ class LocalMangaRepository(private val context: Context) : MangaRepository {
} }
val files = getAvailableStorageDirs(context) val files = getAvailableStorageDirs(context)
.flatMap { x -> x.listFiles(filenameFilter)?.toList().orEmpty() } .flatMap { x -> x.listFiles(filenameFilter)?.toList().orEmpty() }
return files.mapNotNull { x -> safe { getFromFile(x) } } return files.mapNotNull { x -> runCatching { getFromFile(x) }.getOrNull() }
} }
override suspend fun getDetails(manga: Manga) = if (manga.chapters == null) { override suspend fun getDetails(manga: Manga) = if (manga.chapters == null) {
@@ -128,9 +127,9 @@ class LocalMangaRepository(private val context: Context) : MangaRepository {
} }
fun getRemoteManga(localManga: Manga): Manga? { fun getRemoteManga(localManga: Manga): Manga? {
val file = safe { val file = runCatching {
Uri.parse(localManga.url).toFile() Uri.parse(localManga.url).toFile()
} ?: return null }.getOrNull() ?: return null
val zip = ZipFile(file) val zip = ZipFile(file)
val entry = zip.getEntry(MangaZip.INDEX_ENTRY) val entry = zip.getEntry(MangaZip.INDEX_ENTRY)
val index = entry?.let(zip::readText)?.let(::MangaIndex) ?: return null val index = entry?.let(zip::readText)?.let(::MangaIndex) ?: return null

View File

@@ -22,7 +22,6 @@ import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.utils.MediaStoreCompat import org.koitharu.kotatsu.utils.MediaStoreCompat
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.safe
import org.koitharu.kotatsu.utils.ext.sub import org.koitharu.kotatsu.utils.ext.sub
import java.io.IOException import java.io.IOException
@@ -49,7 +48,10 @@ class LocalListViewModel(
list.isEmpty() -> listOf(EmptyState(R.string.text_local_holder)) list.isEmpty() -> listOf(EmptyState(R.string.text_local_holder))
else -> list.toUi(mode) else -> list.toUi(mode)
} }
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState)) }.asLiveDataDistinct(
viewModelScope.coroutineContext + Dispatchers.Default,
listOf(LoadingState)
)
init { init {
onRefresh() onRefresh()
@@ -94,7 +96,7 @@ class LocalListViewModel(
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
val original = repository.getRemoteManga(manga) val original = repository.getRemoteManga(manga)
repository.delete(manga) || throw IOException("Unable to delete file") repository.delete(manga) || throw IOException("Unable to delete file")
safe { runCatching {
historyRepository.deleteOrSwap(manga, original) historyRepository.deleteOrSwap(manga, original)
} }
} }

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.reader.ui package org.koitharu.kotatsu.reader.domain
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory

View File

@@ -5,7 +5,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
abstract class BasePageHolder<B : ViewBinding>( abstract class BasePageHolder<B : ViewBinding>(
protected val binding: B, protected val binding: B,

View File

@@ -8,7 +8,7 @@ import androidx.viewbinding.ViewBinding
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.reader.ui.ReaderViewModel import org.koitharu.kotatsu.reader.ui.ReaderViewModel

View File

@@ -6,7 +6,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.utils.ext.resetTransformations import org.koitharu.kotatsu.utils.ext.resetTransformations
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine

View File

@@ -9,7 +9,7 @@ import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException
import org.koitharu.kotatsu.core.model.MangaPage import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.utils.ext.launchAfter import org.koitharu.kotatsu.utils.ext.launchAfter
import org.koitharu.kotatsu.utils.ext.launchInstead import org.koitharu.kotatsu.utils.ext.launchInstead
import java.io.File import java.io.File

View File

@@ -6,7 +6,7 @@ import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.ItemPageBinding import org.koitharu.kotatsu.databinding.ItemPageBinding
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.pager.standard.PageHolder import org.koitharu.kotatsu.reader.ui.pager.standard.PageHolder
class ReversedPageHolder( class ReversedPageHolder(

View File

@@ -5,7 +5,7 @@ import android.view.ViewGroup
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.ItemPageBinding import org.koitharu.kotatsu.databinding.ItemPageBinding
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
class ReversedPagesAdapter( class ReversedPagesAdapter(

View File

@@ -12,7 +12,7 @@ import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException
import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.ItemPageBinding import org.koitharu.kotatsu.databinding.ItemPageBinding
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.pager.BasePageHolder import org.koitharu.kotatsu.reader.ui.pager.BasePageHolder
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage

View File

@@ -5,7 +5,7 @@ import android.view.ViewGroup
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.ItemPageBinding import org.koitharu.kotatsu.databinding.ItemPageBinding
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
class PagesAdapter( class PagesAdapter(

View File

@@ -5,7 +5,7 @@ import android.view.ViewGroup
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
class WebtoonAdapter( class WebtoonAdapter(

View File

@@ -11,7 +11,7 @@ import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException
import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding
import org.koitharu.kotatsu.reader.ui.PageLoader import org.koitharu.kotatsu.reader.domain.PageLoader
import org.koitharu.kotatsu.reader.ui.pager.BasePageHolder import org.koitharu.kotatsu.reader.ui.pager.BasePageHolder
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage

View File

@@ -7,7 +7,6 @@ import android.view.MenuItem
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity
import org.koitharu.kotatsu.utils.ext.safe
import java.io.Closeable import java.io.Closeable
object SearchHelper { object SearchHelper {
@@ -43,10 +42,10 @@ object SearchHelper {
override fun onSuggestionSelect(position: Int) = false override fun onSuggestionSelect(position: Int) = false
override fun onSuggestionClick(position: Int): Boolean { override fun onSuggestionClick(position: Int): Boolean {
val query = safe { val query = runCatching {
val c = view.suggestionsAdapter.getItem(position) as? Cursor val c = view.suggestionsAdapter.getItem(position) as? Cursor
c?.getString(c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY)) c?.getString(c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY))
} ?: return false }.getOrNull() ?: return false
view.setQuery(query, true) view.setQuery(query, true)
return true return true
} }

View File

@@ -7,9 +7,11 @@ import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.network.cookies.ClearableCookieJar
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.local.data.Cache import org.koitharu.kotatsu.local.data.Cache
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
@@ -71,6 +73,20 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
clearCache(preference, Cache.THUMBS) clearCache(preference, Cache.THUMBS)
true true
} }
AppSettings.KEY_COOKIES_CLEAR -> {
viewLifecycleScope.launch {
val cookieJar = get<ClearableCookieJar>()
withContext(Dispatchers.IO) {
cookieJar.clear()
}
Snackbar.make(
listView ?: return@launch,
R.string.cookies_cleared,
Snackbar.LENGTH_SHORT
).show()
}
true
}
AppSettings.KEY_SEARCH_HISTORY_CLEAR -> { AppSettings.KEY_SEARCH_HISTORY_CLEAR -> {
viewLifecycleScope.launch { viewLifecycleScope.launch {
MangaSuggestionsProvider.clearHistory(preference.context) MangaSuggestionsProvider.clearHistory(preference.context)

View File

@@ -9,18 +9,18 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.utils.ext.mapToSet import org.koitharu.kotatsu.utils.ext.mapToSet
import org.koitharu.kotatsu.utils.ext.safe
class SourcesAdapter( class SourcesAdapter(
private val settings: AppSettings, private val settings: AppSettings,
private val onItemClickListener: OnListItemClickListener<MangaSource>, private val onItemClickListener: OnListItemClickListener<MangaSource>,
) : RecyclerView.Adapter<SourceViewHolder>() { ) : RecyclerView.Adapter<SourceViewHolder>() {
private val dataSet = MangaProviderFactory.getSources(settings, includeHidden = true).toMutableList() private val dataSet =
MangaProviderFactory.getSources(settings, includeHidden = true).toMutableList()
private val hiddenItems = settings.hiddenSources.mapNotNull { private val hiddenItems = settings.hiddenSources.mapNotNull {
safe { runCatching {
MangaSource.valueOf(it) MangaSource.valueOf(it)
} }.getOrNull()
}.toMutableSet() }.toMutableSet()
override fun onCreateViewHolder( override fun onCreateViewHolder(

View File

@@ -21,7 +21,6 @@ import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.ext.safe
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
import org.koitharu.kotatsu.utils.ext.toUriOrNull import org.koitharu.kotatsu.utils.ext.toUriOrNull
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -37,23 +36,23 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
private val repository by inject<TrackingRepository>() private val repository by inject<TrackingRepository>()
private val settings by inject<AppSettings>() private val settings by inject<AppSettings>()
override suspend fun doWork(): Result = withContext(Dispatchers.Default) { override suspend fun doWork(): Result {
val trackSources = settings.trackSources val trackSources = settings.trackSources
if (trackSources.isEmpty()) { if (trackSources.isEmpty()) {
return@withContext Result.success() return Result.success()
} }
val tracks = repository.getAllTracks( val tracks = repository.getAllTracks(
useFavourites = AppSettings.TRACK_FAVOURITES in trackSources, useFavourites = AppSettings.TRACK_FAVOURITES in trackSources,
useHistory = AppSettings.TRACK_HISTORY in trackSources useHistory = AppSettings.TRACK_HISTORY in trackSources
) )
if (tracks.isEmpty()) { if (tracks.isEmpty()) {
return@withContext Result.success() return Result.success()
} }
var success = 0 var success = 0
for (track in tracks) { for (track in tracks) {
val details = safe { val details = runCatching {
track.manga.source.repository.getDetails(track.manga) track.manga.source.repository.getDetails(track.manga)
} }.getOrNull()
val chapters = details?.chapters ?: continue val chapters = details?.chapters ?: continue
when { when {
track.knownChaptersCount == -1 -> { //first check track.knownChaptersCount == -1 -> { //first check
@@ -125,7 +124,7 @@ class TrackWorker(context: Context, workerParams: WorkerParameters) :
success++ success++
} }
repository.cleanup() repository.cleanup()
if (success == 0) { return if (success == 0) {
Result.retry() Result.retry()
} else { } else {
Result.success() Result.success()

View File

@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.utils.ext
import android.content.res.Resources import android.content.res.Resources
import android.util.Log import android.util.Log
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
@@ -12,15 +11,6 @@ import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
inline fun <T, R> T.safe(action: T.() -> R?) = try {
this.action()
} catch (e: Throwable) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}
null
}
suspend inline fun <T, R> T.retryUntilSuccess(maxAttempts: Int, action: T.() -> R): R { suspend inline fun <T, R> T.retryUntilSuccess(maxAttempts: Int, action: T.() -> R): R {
var attempts = maxAttempts var attempts = maxAttempts
while (true) { while (true) {

View File

@@ -35,11 +35,11 @@ inline fun File.findParent(predicate: (File) -> Boolean): File? {
return current return current
} }
fun File.getStorageName(context: Context): String = safe { fun File.getStorageName(context: Context): String = runCatching {
val manager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager val manager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
manager.getStorageVolume(this)?.getDescription(context)?.let { manager.getStorageVolume(this)?.getDescription(context)?.let {
return@safe it return@runCatching it
} }
} }
when { when {
@@ -47,6 +47,6 @@ fun File.getStorageName(context: Context): String = safe {
Environment.isExternalStorageRemovable(this) -> context.getString(R.string.external_storage) Environment.isExternalStorageRemovable(this) -> context.getString(R.string.external_storage)
else -> null else -> null
} }
} ?: context.getString(R.string.other_storage) }.getOrNull() ?: context.getString(R.string.other_storage)
fun Uri.toFileOrNull() = if (scheme == "file") path?.let(::File) else null fun Uri.toFileOrNull() = if (scheme == "file") path?.let(::File) else null

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_list_mode"
android:orderInCategory="20"
android:title="@string/list_mode"
app:showAsAction="never" />
</menu>

View File

@@ -190,4 +190,6 @@
<string name="silent">Без звука</string> <string name="silent">Без звука</string>
<string name="captcha_required">Необходимо пройти CAPTCHA</string> <string name="captcha_required">Необходимо пройти CAPTCHA</string>
<string name="resolve">Resolve</string> <string name="resolve">Resolve</string>
<string name="clear_cookies">Очистить куки</string>
<string name="cookies_cleared">Все куки удалены</string>
</resources> </resources>

View File

@@ -192,4 +192,6 @@
<string name="silent">Silent</string> <string name="silent">Silent</string>
<string name="captcha_required">CAPTCHA is required</string> <string name="captcha_required">CAPTCHA is required</string>
<string name="resolve">Resolve</string> <string name="resolve">Resolve</string>
<string name="clear_cookies">Clear cookies</string>
<string name="cookies_cleared">All cookies was removed</string>
</resources> </resources>

View File

@@ -8,13 +8,6 @@
<item name="android:paddingBottom">10dp</item> <item name="android:paddingBottom">10dp</item>
</style> </style>
<style name="AppToggleButton.Vertical" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="android:checkable">true</item>
<item name="android:gravity">center_horizontal</item>
<item name="iconPadding">6dp</item>
<item name="iconGravity">top</item>
</style>
<style name="AppPopupTheme" parent="ThemeOverlay.MaterialComponents.Light" /> <style name="AppPopupTheme" parent="ThemeOverlay.MaterialComponents.Light" />
<style name="AppToolbarTheme" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar"> <style name="AppToolbarTheme" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar">
@@ -32,8 +25,4 @@
<item name="background">@color/grey</item> <item name="background">@color/grey</item>
</style> </style>
<style name="AppBadge" parent="Widget.MaterialComponents.Badge">
<item name="backgroundColor">?attr/colorAccent</item>
</style>
</resources> </resources>

View File

@@ -16,8 +16,8 @@
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<PreferenceCategory <PreferenceCategory
app:iconSpaceReserved="false" android:title="@string/cache"
android:title="@string/cache"> app:iconSpaceReserved="false">
<Preference <Preference
android:key="thumbs_cache_clear" android:key="thumbs_cache_clear"
@@ -33,4 +33,10 @@
</PreferenceCategory> </PreferenceCategory>
<Preference
android:key="cookies_clear"
android:persistent="false"
android:title="@string/clear_cookies"
app:iconSpaceReserved="false" />
</PreferenceScreen> </PreferenceScreen>