Update parsers

This commit is contained in:
Koitharu
2022-05-25 10:04:06 +03:00
parent 0c07e649bf
commit 4f3fef3bfe
13 changed files with 103 additions and 54 deletions

View File

@@ -64,9 +64,16 @@ android {
unitTests.returnDefaultValues = false unitTests.returnDefaultValues = false
} }
} }
afterEvaluate {
compileDebugKotlin {
kotlinOptions {
freeCompilerArgs += ['-opt-in=kotlin.RequiresOptIn']
}
}
}
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation('com.github.nv95:kotatsu-parsers:f46c5add46') { implementation('com.github.nv95:kotatsu-parsers:ab87a50e9b') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.core.parser package org.koitharu.kotatsu.core.parser
import java.util.* import java.util.*
import org.koitharu.kotatsu.parsers.InternalParsersApi
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.ConfigKey
@@ -9,6 +10,7 @@ import org.koitharu.kotatsu.parsers.model.*
/** /**
* This parser is just for parser development, it should not be used in releases * This parser is just for parser development, it should not be used in releases
*/ */
@OptIn(InternalParsersApi::class)
class DummyParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.DUMMY) { class DummyParser(override val context: MangaLoaderContext) : MangaParser(MangaSource.DUMMY) {
override val configKeyDomain: ConfigKey.Domain override val configKeyDomain: ConfigKey.Domain
@@ -25,7 +27,7 @@ class DummyParser(override val context: MangaLoaderContext) : MangaParser(MangaS
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder? sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View File

@@ -0,0 +1,10 @@
package org.koitharu.kotatsu.core.model
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.toTitleCase
import java.util.*
fun MangaSource.getLocaleTitle(): String? {
val lc = Locale(locale ?: return null)
return lc.getDisplayLanguage(lc).toTitleCase(lc)
}

View File

@@ -1,11 +1,11 @@
package org.koitharu.kotatsu.core.parser package org.koitharu.kotatsu.core.parser
import java.lang.ref.WeakReference
import java.util.*
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import java.lang.ref.WeakReference
import java.util.*
interface MangaRepository { interface MangaRepository {
@@ -13,12 +13,9 @@ interface MangaRepository {
val sortOrders: Set<SortOrder> val sortOrders: Set<SortOrder>
suspend fun getList( suspend fun getList(offset: Int, query: String?): List<Manga>
offset: Int,
query: String? = null, suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga>
tags: Set<MangaTag>? = null,
sortOrder: SortOrder? = null,
): List<Manga>
suspend fun getDetails(manga: Manga): Manga suspend fun getDetails(manga: Manga): Manga

View File

@@ -20,12 +20,13 @@ class RemoteMangaRepository(private val parser: MangaParser) : MangaRepository {
getConfig().defaultSortOrder = value getConfig().defaultSortOrder = value
} }
override suspend fun getList( override suspend fun getList(offset: Int, query: String?): List<Manga> {
offset: Int, return parser.getList(offset, query)
query: String?, }
tags: Set<MangaTag>?,
sortOrder: SortOrder?, override suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> {
): List<Manga> = parser.getList(offset, query, tags, sortOrder) return parser.getList(offset, tags, sortOrder)
}
override suspend fun getDetails(manga: Manga): Manga = parser.getDetails(manga) override suspend fun getDetails(manga: Manga): Manga = parser.getDetails(manga)

View File

@@ -37,28 +37,25 @@ class LocalMangaRepository(private val storageManager: LocalStorageManager) : Ma
private val filenameFilter = CbzFilter() private val filenameFilter = CbzFilter()
private val locks = CompositeMutex<Long>() private val locks = CompositeMutex<Long>()
override suspend fun getList( override suspend fun getList(offset: Int, query: String?): List<Manga> {
offset: Int,
query: String?,
tags: Set<MangaTag>?,
sortOrder: SortOrder?
): List<Manga> {
if (offset > 0) { if (offset > 0) {
return emptyList() return emptyList()
} }
val files = getAllFiles() val list = getRawList()
val list = coroutineScope {
val dispatcher = Dispatchers.IO.limitedParallelism(MAX_PARALLELISM)
files.map { file ->
getFromFileAsync(file, dispatcher)
}.awaitAll()
}.filterNotNullTo(ArrayList(files.size))
if (!query.isNullOrEmpty()) { if (!query.isNullOrEmpty()) {
list.retainAll { x -> list.retainAll { x ->
x.title.contains(query, ignoreCase = true) || x.title.contains(query, ignoreCase = true) ||
x.altTitle?.contains(query, ignoreCase = true) == true x.altTitle?.contains(query, ignoreCase = true) == true
} }
} }
return list
}
override suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> {
if (offset > 0) {
return emptyList()
}
val list = getRawList()
if (!tags.isNullOrEmpty()) { if (!tags.isNullOrEmpty()) {
list.retainAll { x -> list.retainAll { x ->
x.tags.containsAll(tags) x.tags.containsAll(tags)
@@ -244,7 +241,7 @@ class LocalMangaRepository(private val storageManager: LocalStorageManager) : Ma
} }
} }
override val sortOrders = emptySet<SortOrder>() override val sortOrders = setOf(SortOrder.ALPHABETICAL)
override suspend fun getPageUrl(page: MangaPage) = page.url override suspend fun getPageUrl(page: MangaPage) = page.url
@@ -295,6 +292,16 @@ class LocalMangaRepository(private val storageManager: LocalStorageManager) : Ma
locks.unlock(id) locks.unlock(id)
} }
private suspend fun getRawList(): ArrayList<Manga> {
val files = getAllFiles()
return coroutineScope {
val dispatcher = Dispatchers.IO.limitedParallelism(MAX_PARALLELISM)
files.map { file ->
getFromFileAsync(file, dispatcher)
}.awaitAll()
}.filterNotNullTo(ArrayList(files.size))
}
private suspend fun getAllFiles() = storageManager.getReadableDirs().flatMap { dir -> private suspend fun getAllFiles() = storageManager.getReadableDirs().flatMap { dir ->
dir.listFiles(filenameFilter)?.toList().orEmpty() dir.listFiles(filenameFilter)?.toList().orEmpty()
} }

View File

@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.local.ui
import android.net.Uri import android.net.Uri
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import java.io.IOException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -24,6 +23,7 @@ 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.printStackTraceDebug import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.progress.Progress import org.koitharu.kotatsu.utils.progress.Progress
import java.io.IOException
class LocalListViewModel( class LocalListViewModel(
private val repository: LocalMangaRepository, private val repository: LocalMangaRepository,
@@ -115,7 +115,7 @@ class LocalListViewModel(
private suspend fun doRefresh() { private suspend fun doRefresh() {
try { try {
listError.value = null listError.value = null
mangaList.value = repository.getList(0) mangaList.value = repository.getList(0, null, null)
} catch (e: Throwable) { } catch (e: Throwable) {
listError.value = e listError.value = e
} }

View File

@@ -17,7 +17,6 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.levenshteinDistance import org.koitharu.kotatsu.parsers.util.levenshteinDistance
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
@@ -35,7 +34,6 @@ class MangaSearchRepository(
MangaRepository(source).getList( MangaRepository(source).getList(
offset = 0, offset = 0,
query = query, query = query,
sortOrder = SortOrder.POPULARITY
) )
}.getOrElse { }.getOrElse {
emptyList() emptyList()
@@ -141,4 +139,4 @@ class MangaSearchRepository(
return false return false
} }
} }
} }

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.settings.newsources
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.getLocaleTitle
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
@@ -33,7 +34,7 @@ class NewSourcesViewModel(
sources.value = initialList.map { sources.value = initialList.map {
SourceConfigItem.SourceItem( SourceConfigItem.SourceItem(
source = it, source = it,
summary = null, summary = it.getLocaleTitle(),
isEnabled = it.name !in hidden, isEnabled = it.name !in hidden,
isDraggable = false, isDraggable = false,
) )

View File

@@ -4,6 +4,7 @@ import androidx.core.os.LocaleListCompat
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.getLocaleTitle
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.parsers.util.toTitleCase
@@ -82,7 +83,7 @@ class SourcesSettingsViewModel(
} }
SourceConfigItem.SourceItem( SourceConfigItem.SourceItem(
source = it, source = it,
summary = null, summary = it.getLocaleTitle(),
isEnabled = it.name !in hiddenSources, isEnabled = it.name !in hiddenSources,
isDraggable = false, isDraggable = false,
) )
@@ -105,7 +106,7 @@ class SourcesSettingsViewModel(
enabledSources.mapTo(result) { enabledSources.mapTo(result) {
SourceConfigItem.SourceItem( SourceConfigItem.SourceItem(
source = it, source = it,
summary = getLocaleTitle(it.locale), summary = it.getLocaleTitle(),
isEnabled = true, isEnabled = true,
isDraggable = true, isDraggable = true,
) )
@@ -162,4 +163,4 @@ class SourcesSettingsViewModel(
} }
} }
} }
} }

View File

@@ -17,15 +17,17 @@ import org.koitharu.kotatsu.databinding.ItemSourceConfigBinding
import org.koitharu.kotatsu.databinding.ItemSourceConfigDraggableBinding import org.koitharu.kotatsu.databinding.ItemSourceConfigDraggableBinding
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.textAndVisible
fun sourceConfigHeaderDelegate() = adapterDelegateViewBinding<SourceConfigItem.Header, SourceConfigItem, ItemFilterHeaderBinding>( fun sourceConfigHeaderDelegate() =
{ layoutInflater, parent -> ItemFilterHeaderBinding.inflate(layoutInflater, parent, false) } adapterDelegateViewBinding<SourceConfigItem.Header, SourceConfigItem, ItemFilterHeaderBinding>(
) { { layoutInflater, parent -> ItemFilterHeaderBinding.inflate(layoutInflater, parent, false) }
) {
bind { bind {
binding.textViewTitle.setText(item.titleResId) binding.textViewTitle.setText(item.titleResId)
}
} }
}
fun sourceConfigGroupDelegate( fun sourceConfigGroupDelegate(
listener: SourceConfigListener, listener: SourceConfigListener,
@@ -61,6 +63,7 @@ fun sourceConfigItemDelegate(
bind { bind {
binding.textViewTitle.text = item.source.title binding.textViewTitle.text = item.source.title
binding.switchToggle.isChecked = item.isEnabled binding.switchToggle.isChecked = item.isEnabled
binding.textViewDescription.textAndVisible = item.summary
imageRequest = ImageRequest.Builder(context) imageRequest = ImageRequest.Builder(context)
.data(item.faviconUrl) .data(item.faviconUrl)
.error(R.drawable.ic_favicon_fallback) .error(R.drawable.ic_favicon_fallback)

View File

@@ -3,8 +3,9 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:minHeight="?android:listPreferredItemHeightSmall"
android:orientation="horizontal"> android:orientation="horizontal">
<ImageView <ImageView
@@ -17,16 +18,35 @@
android:scaleType="fitCenter" android:scaleType="fitCenter"
tools:src="@tools:sample/avatars" /> tools:src="@tools:sample/avatars" />
<TextView <LinearLayout
android:id="@+id/textView_title"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:layout_marginStart="?android:listPreferredItemPaddingStart"
android:layout_marginEnd="?android:listPreferredItemPaddingEnd"
android:layout_weight="1" android:layout_weight="1"
android:ellipsize="marquee" android:orientation="vertical">
android:fadingEdge="horizontal"
android:singleLine="true" <TextView
android:textAppearance="?attr/textAppearanceBodyLarge" android:id="@+id/textView_title"
tools:text="@tools:sample/lorem[1]" /> android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodyLarge"
tools:text="@tools:sample/lorem[15]" />
<TextView
android:id="@+id/textView_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
tools:text="English" />
</LinearLayout>
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_toggle" android:id="@+id/switch_toggle"

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/navigation_header" android:id="@+id/navigation_header"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -16,7 +17,8 @@
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:layout_marginBottom="24dp" android:layout_marginBottom="24dp"
android:contentDescription="@string/app_name" android:contentDescription="@string/app_name"
android:src="@drawable/ic_totoro" /> android:src="@drawable/ic_totoro"
app:tint="?colorPrimary" />
<TextView <TextView
android:id="@+id/textView_title" android:id="@+id/textView_title"