Extract source api from app module (#8014)

* Extract source api from app module

* Extract source online api from app module

(cherry picked from commit 86fe85079413f8ed6e1109b46e6131a9b788b988)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt
#	core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt
#	source-api/src/main/java/eu/kanade/tachiyomi/source/Source.kt
#	source-api/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt
This commit is contained in:
Andreas 2022-09-16 00:12:27 +02:00 committed by Jobobby04
parent b975b9b86f
commit 8a322ea28e
117 changed files with 547 additions and 422 deletions

View File

@ -133,6 +133,8 @@ android {
dependencies {
implementation(project(":i18n"))
implementation(project(":core"))
implementation(project(":source-api"))
// Compose
implementation(compose.activity)

View File

@ -20,6 +20,7 @@ import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
import eu.kanade.domain.manga.interactor.GetFavoriteEntries
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetIdsOfFavoriteMangaWithMetadata
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMangaBySource
import eu.kanade.domain.manga.interactor.GetMergedManga
import eu.kanade.domain.manga.interactor.GetMergedMangaById
@ -65,6 +66,7 @@ import eu.kanade.domain.source.interactor.ToggleExcludeFromDataSaver
import eu.kanade.domain.source.interactor.ToggleSources
import eu.kanade.domain.source.repository.FeedSavedSearchRepository
import eu.kanade.domain.source.repository.SavedSearchRepository
import eu.kanade.tachiyomi.source.online.MetadataSource
import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addFactory
@ -100,6 +102,11 @@ class SYDomainModule : InjektModule {
addFactory { ReorderSortTag(get(), get()) }
addFactory { GetPagePreviews(get()) }
// Required for [MetadataSource]
addFactory<MetadataSource.GetMangaId> { GetManga(get()) }
addFactory<MetadataSource.GetFlatMetadataById> { GetFlatMetadataById(get()) }
addFactory<MetadataSource.InsertFlatMetadata> { InsertFlatMetadata(get()) }
addSingletonFactory<MangaMetadataRepository> { MangaMetadataRepositoryImpl(get()) }
addFactory { GetFlatMetadataById(get()) }
addFactory { InsertFlatMetadata(get()) }

View File

@ -1,6 +1,7 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.repository.MangaMetadataRepository
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.util.system.logcat
import exh.metadata.metadata.base.FlatMetadata
import kotlinx.coroutines.flow.Flow
@ -9,9 +10,9 @@ import logcat.LogPriority
class GetFlatMetadataById(
private val mangaMetadataRepository: MangaMetadataRepository,
) {
) : MetadataSource.GetFlatMetadataById {
suspend fun await(id: Long): FlatMetadata? {
override suspend fun await(id: Long): FlatMetadata? {
return try {
val meta = mangaMetadataRepository.getMetadataById(id)
return if (meta != null) {

View File

@ -2,13 +2,14 @@ package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.Flow
import logcat.LogPriority
class GetManga(
private val mangaRepository: MangaRepository,
) {
) : MetadataSource.GetMangaId {
suspend fun await(id: Long): Manga? {
return try {
@ -30,4 +31,10 @@ class GetManga(
fun subscribe(url: String, sourceId: Long): Flow<Manga?> {
return mangaRepository.getMangaByUrlAndSourceIdAsFlow(url, sourceId)
}
// SY -->
override suspend fun awaitId(url: String, sourceId: Long): Long? {
return await(url, sourceId)?.id
}
// SY <--
}

View File

@ -1,6 +1,7 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.repository.MangaMetadataRepository
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.util.system.logcat
import exh.metadata.metadata.base.FlatMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
@ -8,7 +9,7 @@ import logcat.LogPriority
class InsertFlatMetadata(
private val mangaMetadataRepository: MangaMetadataRepository,
) {
) : MetadataSource.InsertFlatMetadata {
suspend fun await(flatMetadata: FlatMetadata) {
try {
@ -18,7 +19,7 @@ class InsertFlatMetadata(
}
}
suspend fun await(metadata: RaisedSearchMetadata) {
override suspend fun await(metadata: RaisedSearchMetadata) {
try {
mangaMetadataRepository.insertMetadata(metadata)
} catch (e: Exception) {

View File

@ -72,14 +72,33 @@ import eu.kanade.presentation.util.isScrollingUp
import eu.kanade.presentation.util.plus
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.getNameForMangaInfo
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.source.online.all.Hitomi
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.source.online.all.NHentai
import eu.kanade.tachiyomi.source.online.all.PervEden
import eu.kanade.tachiyomi.source.online.english.EightMuses
import eu.kanade.tachiyomi.source.online.english.HBrowse
import eu.kanade.tachiyomi.source.online.english.Pururin
import eu.kanade.tachiyomi.source.online.english.Tsumino
import eu.kanade.tachiyomi.ui.manga.ChapterItem
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.ui.manga.PagePreviewState
import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource
import exh.ui.metadata.adapters.EHentaiDescription
import exh.ui.metadata.adapters.EightMusesDescription
import exh.ui.metadata.adapters.HBrowseDescription
import exh.ui.metadata.adapters.HitomiDescription
import exh.ui.metadata.adapters.MangaDexDescription
import exh.ui.metadata.adapters.NHentaiDescription
import exh.ui.metadata.adapters.PervEdenDescription
import exh.ui.metadata.adapters.PururinDescription
import exh.ui.metadata.adapters.TsuminoDescription
@Composable
fun MangaScreen(
@ -255,7 +274,7 @@ private fun MangaScreenSmallImpl(
val chapters = remember(state) { state.processedChapters.toList() }
// SY -->
val metadataSource = remember(state.source.id) { state.source.getMainSource<MetadataSource<*, *>>() }
val metadataDescription = metadataDescription(state.source)
// SY <--
val internalOnBackPressed = {
@ -408,12 +427,12 @@ private fun MangaScreenSmallImpl(
}
// SY -->
if (metadataSource != null) {
if (metadataDescription != null) {
item(
key = MangaScreenItem.METADATA_INFO,
contentType = MangaScreenItem.METADATA_INFO,
) {
metadataSource.DescriptionComposable(
metadataDescription(
state = state,
openMetadataViewer = onMetadataViewerClicked,
search = { onSearch(it, false) },
@ -540,7 +559,7 @@ fun MangaScreenLargeImpl(
val chapters = remember(state) { state.processedChapters.toList() }
// SY -->
val metadataSource = remember(state.source.id) { state.source.getMainSource<MetadataSource<*, *>>() }
val metadataDescription = metadataDescription(state.source)
// SY <--
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
@ -681,7 +700,7 @@ fun MangaScreenLargeImpl(
// SY <--
)
// SY -->
metadataSource?.DescriptionComposable(
metadataDescription?.invoke(
state = state,
openMetadataViewer = onMetadataViewerClicked,
search = { onSearch(it, false) },
@ -847,3 +866,42 @@ private fun onChapterItemClick(
else -> onChapterClicked(chapterItem.chapter)
}
}
typealias MetadataDescriptionComposable = @Composable (state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) -> Unit
@Composable
fun metadataDescription(source: Source): MetadataDescriptionComposable? {
val metadataSource = remember(source.id) { source.getMainSource<MetadataSource<*, *>>() }
return remember(metadataSource) {
when (metadataSource) {
is EHentai -> { state, openMetadataViewer, search ->
EHentaiDescription(state, openMetadataViewer, search)
}
is Hitomi -> { state, openMetadataViewer, _ ->
HitomiDescription(state, openMetadataViewer)
}
is MangaDex -> { state, openMetadataViewer, _ ->
MangaDexDescription(state, openMetadataViewer)
}
is NHentai -> { state, openMetadataViewer, _ ->
NHentaiDescription(state, openMetadataViewer)
}
is PervEden -> { state, openMetadataViewer, _ ->
PervEdenDescription(state, openMetadataViewer)
}
is EightMuses -> { state, openMetadataViewer, _ ->
EightMusesDescription(state, openMetadataViewer)
}
is HBrowse -> { state, openMetadataViewer, _ ->
HBrowseDescription(state, openMetadataViewer)
}
is Pururin -> { state, openMetadataViewer, _ ->
PururinDescription(state, openMetadataViewer)
}
is Tsumino -> { state, openMetadataViewer, _ ->
TsuminoDescription(state, openMetadataViewer)
}
else -> null
}
}
}

View File

@ -48,6 +48,7 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.copyFrom
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.system.toLong

View File

@ -1,6 +1,10 @@
package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.source.model.SManga
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
open class MangaImpl : Manga {
@ -77,6 +81,19 @@ open class MangaImpl : Manga {
private set
var ogStatus: Int = 0
private set
override val originalTitle: String
get() = ogTitle
override val originalAuthor: String?
get() = ogAuthor ?: author
override val originalArtist: String?
get() = ogArtist ?: artist
override val originalDescription: String?
get() = ogDesc ?: description
override val originalGenre: String?
get() = ogGenre ?: genre
override val originalStatus: Int
get() = ogStatus
// SY <--
override fun equals(other: Any?): Boolean {
@ -93,6 +110,18 @@ open class MangaImpl : Manga {
}
// SY -->
override fun copyFrom(other: SManga) {
// EXH -->
if (other.title.isNotBlank() && originalTitle != other.title) {
val source = (this as? Manga)?.source
if (source != null) {
Injekt.get<DownloadManager>().renameMangaDir(originalTitle, other.originalTitle, source)
}
}
// EXH <--
super.copyFrom(other)
}
companion object {
private val customMangaManager: CustomMangaManager by injectLazy()
}

View File

@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.isDevFlavor
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
import java.io.File
import java.text.DateFormat

View File

@ -0,0 +1,58 @@
package eu.kanade.tachiyomi.source
import android.graphics.drawable.Drawable
import eu.kanade.domain.source.model.SourceData
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.ExtensionManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
fun Source.icon(): Drawable? = Injekt.get<ExtensionManager>().getAppIconForSource(this)
fun Source.getPreferenceKey(): String = "source_$id"
fun Source.toSourceData(): SourceData = SourceData(id = id, lang = lang, name = name)
fun Source.getNameForMangaInfo(mergeSources: List<Source>?): String {
val preferences = Injekt.get<PreferencesHelper>()
val enabledLanguages = preferences.enabledLanguages().get()
.filterNot { it in listOf("all", "other") }
val hasOneActiveLanguages = enabledLanguages.size == 1
val isInEnabledLanguages = lang in enabledLanguages
return when {
// SY -->
!mergeSources.isNullOrEmpty() -> getMergedSourcesString(
mergeSources,
enabledLanguages,
hasOneActiveLanguages,
)
// SY <--
// For edge cases where user disables a source they got manga of in their library.
hasOneActiveLanguages && !isInEnabledLanguages -> toString()
// Hide the language tag when only one language is used.
hasOneActiveLanguages && isInEnabledLanguages -> name
else -> toString()
}
}
// SY -->
private fun getMergedSourcesString(
mergeSources: List<Source>,
enabledLangs: List<String>,
onlyName: Boolean,
): String {
return if (onlyName) {
mergeSources.joinToString { source ->
if (source.lang !in enabledLangs) {
source.toString()
} else {
source.name
}
}
} else {
mergeSources.joinToString()
}
}
// SY <--
fun Source.isLocalOrStub(): Boolean = id == LocalSource.ID || this is SourceManager.StubSource

View File

@ -133,6 +133,7 @@ class SourceManager(
val enhancedSource = EnhancedHttpSource(
this,
delegate.newSourceClass.constructors.find { it.parameters.size == 2 }!!.call(this, context),
::delegateSources,
)
currentDelegatedSources[enhancedSource.originalSource.id] = DelegatedSource(
@ -156,6 +157,8 @@ class SourceManager(
// EXH <--
}
private fun delegateSources() = preferences.delegateSources().get()
fun get(sourceKey: Long): Source? {
return sourcesMap[sourceKey]
}

View File

@ -0,0 +1,11 @@
package eu.kanade.tachiyomi.source.model
import data.Chapters
fun SChapter.copyFrom(other: Chapters) {
name = other.name
url = other.url
date_upload = other.date_upload
chapter_number = other.chapter_number
scanlator = other.scanlator
}

View File

@ -0,0 +1,46 @@
package eu.kanade.tachiyomi.source.model
import data.Mangas
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
fun SManga.copyFrom(other: Mangas) {
// EXH -->
if (other.title.isNotBlank() && originalTitle != other.title) {
val oldTitle = originalTitle
title = other.title
val source = (this as? Manga)?.source
if (source != null) {
Injekt.get<DownloadManager>().renameMangaDir(oldTitle, other.title, source)
}
}
// EXH <--
if (other.author != null) {
author = other.author
}
if (other.artist != null) {
artist = other.artist
}
if (other.description != null) {
description = other.description
}
if (other.genre != null) {
genre = other.genre.joinToString(separator = ", ")
}
if (other.thumbnail_url != null) {
thumbnail_url = other.thumbnail_url
}
status = other.status.toInt()
if (!initialized) {
initialized = other.initialized
}
}

View File

@ -1,9 +0,0 @@
package eu.kanade.tachiyomi.source.online
import androidx.recyclerview.widget.RecyclerView
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.base.controller.BaseController
interface BrowseSourceFilterHeader : CatalogueSource {
fun getFilterHeader(controller: BaseController<*>, onClick: () -> Unit): RecyclerView.Adapter<*>
}

View File

@ -4,7 +4,6 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl
@ -28,7 +27,6 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.NamespaceSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.lang.withIOContext
@ -51,7 +49,6 @@ import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUA
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.toGenreString
import exh.metadata.metadata.base.RaisedTag
import exh.ui.login.EhLoginActivity
import exh.ui.metadata.adapters.EHentaiDescription
import exh.util.UriFilter
import exh.util.UriGroup
import exh.util.asObservableWithAsyncStacktrace
@ -1127,11 +1124,6 @@ class EHentai(
return "${uri.scheme}://${uri.host}/g/${obj["gid"]!!.jsonPrimitive.int}/${obj["token"]!!.jsonPrimitive.content}/"
}
@Composable
override fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
EHentaiDescription(state, openMetadataViewer, search)
}
override suspend fun getPagePreviewList(
manga: SManga,
page: Int,

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.source.online.all
import android.content.Context
import android.net.Uri
import android.os.Build
import androidx.compose.runtime.Composable
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
@ -11,13 +10,11 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.NamespaceSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.metadata.HitomiSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.HitomiDescription
import exh.util.urlImportFetchSearchManga
import org.jsoup.nodes.Document
import java.text.SimpleDateFormat
@ -136,11 +133,6 @@ class Hitomi(delegate: HttpSource, val context: Context) :
return "https://hitomi.la/manga/${uri.pathSegments[1].substringBefore('.')}.html"
}
@Composable
override fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
HitomiDescription(state, openMetadataViewer)
}
companion object {
const val otherId = 2703068117101782422L
private val DATE_FORMAT by lazy {

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.source.online.all
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import androidx.compose.runtime.Composable
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
@ -14,7 +13,6 @@ import eu.kanade.tachiyomi.source.model.MetadataMangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.BrowseSourceFilterHeader
import eu.kanade.tachiyomi.source.online.FollowsSource
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LoginSource
@ -22,10 +20,7 @@ import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.NamespaceSource
import eu.kanade.tachiyomi.source.online.RandomMangaSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.lang.runAsObservable
import exh.md.MangaDexFabHeaderAdapter
import exh.md.dto.MangaDto
import exh.md.dto.StatisticsMangaDto
import exh.md.handlers.ApiMangaParser
@ -49,7 +44,6 @@ import exh.md.utils.MdLang
import exh.md.utils.MdUtil
import exh.metadata.metadata.MangaDexSearchMetadata
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.MangaDexDescription
import okhttp3.OkHttpClient
import okhttp3.Response
import rx.Observable
@ -64,7 +58,6 @@ class MangaDex(delegate: HttpSource, val context: Context) :
UrlImportableSource,
FollowsSource,
LoginSource,
BrowseSourceFilterHeader,
RandomMangaSource,
NamespaceSource {
override val lang: String = delegate.lang
@ -217,11 +210,6 @@ class MangaDex(delegate: HttpSource, val context: Context) :
// MetadataSource methods
override val metaClass: KClass<MangaDexSearchMetadata> = MangaDexSearchMetadata::class
@Composable
override fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
MangaDexDescription(state, openMetadataViewer)
}
override suspend fun parseIntoMetadata(metadata: MangaDexSearchMetadata, input: Triple<MangaDto, List<String>, StatisticsMangaDto>) {
apiMangaParser.parseIntoMetadata(metadata, input.first, input.second, input.third)
}
@ -272,11 +260,11 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return followsHandler.fetchAllFollows()
}
override suspend fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Boolean {
suspend fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Boolean {
return followsHandler.updateFollowStatus(mangaID, followStatus)
}
override suspend fun fetchTrackingInfo(url: String): Track {
suspend fun fetchTrackingInfo(url: String): Track {
return followsHandler.fetchTrackingInfo(url)
}
@ -293,11 +281,6 @@ class MangaDex(delegate: HttpSource, val context: Context) :
return mangaHandler.getTrackingInfo(track)
}
// BrowseSourceFilterHeader method
override fun getFilterHeader(controller: BaseController<*>, onClick: () -> Unit): MangaDexFabHeaderAdapter {
return MangaDexFabHeaderAdapter(controller, this, onClick)
}
// RandomMangaSource method
override suspend fun fetchRandomMangaUrl(): String {
return mangaHandler.fetchRandomMangaId()

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.source.online.all
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import androidx.compose.runtime.Composable
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.newCallWithProgress
@ -16,12 +15,10 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.NamespaceSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import exh.metadata.metadata.NHentaiSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.NHentaiDescription
import exh.util.trimOrNull
import exh.util.urlImportFetchSearchManga
import kotlinx.serialization.SerialName
@ -175,11 +172,6 @@ class NHentai(delegate: HttpSource, val context: Context) :
return "$baseUrl/g/${uri.pathSegments[1]}/"
}
@Composable
override fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
NHentaiDescription(state, openMetadataViewer)
}
override suspend fun getPagePreviewList(manga: SManga, page: Int): PagePreviewPage {
val metadata = fetchOrLoadMetadata(manga.id()) {
client.newCall(mangaDetailsRequest(manga)).await()

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.source.online.all
import android.content.Context
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.core.net.toUri
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.FilterList
@ -10,13 +9,11 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.metadata.PervEdenSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.PervEdenDescription
import exh.util.urlImportFetchSearchManga
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
@ -132,9 +129,4 @@ class PervEden(delegate: HttpSource, val context: Context) :
}
return newUri.toString()
}
@Composable
override fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
PervEdenDescription(state, openMetadataViewer)
}
}

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.source.online.english
import android.content.Context
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.core.net.toUri
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.FilterList
@ -11,12 +10,10 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.NamespaceSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.metadata.EightMusesSearchMetadata
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.EightMusesDescription
import exh.util.urlImportFetchSearchManga
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
@ -96,9 +93,4 @@ class EightMuses(delegate: HttpSource, val context: Context) :
}
return "/comics/album/${path.joinToString("/")}"
}
@Composable
override fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
EightMusesDescription(state, openMetadataViewer)
}
}

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.source.online.english
import android.content.Context
import android.net.Uri
import androidx.compose.runtime.Composable
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SManga
@ -10,12 +9,10 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.NamespaceSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.metadata.HBrowseSearchMetadata
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.HBrowseDescription
import exh.util.urlImportFetchSearchManga
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
@ -84,9 +81,4 @@ class HBrowse(delegate: HttpSource, val context: Context) :
override suspend fun mapUrlToMangaUrl(uri: Uri): String? {
return uri.pathSegments.firstOrNull()?.let { "/$it/c00001/" }
}
@Composable
override fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
HBrowseDescription(state, openMetadataViewer)
}
}

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.source.online.english
import android.content.Context
import android.net.Uri
import androidx.compose.runtime.Composable
import androidx.core.net.toUri
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.FilterList
@ -12,13 +11,11 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.NamespaceSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.metadata.PururinSearchMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.PururinDescription
import exh.util.dropBlank
import exh.util.trimAll
import exh.util.urlImportFetchSearchManga
@ -116,9 +113,4 @@ class Pururin(delegate: HttpSource, val context: Context) :
override suspend fun mapUrlToMangaUrl(uri: Uri): String {
return "${PururinSearchMetadata.BASE_URL}/gallery/${uri.pathSegments.getOrNull(1)}/${uri.lastPathSegment}"
}
@Composable
override fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
PururinDescription(state, openMetadataViewer)
}
}

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.source.online.english
import android.content.Context
import android.net.Uri
import androidx.compose.runtime.Composable
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
@ -11,14 +10,12 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.source.online.NamespaceSource
import eu.kanade.tachiyomi.source.online.UrlImportableSource
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.asJsoup
import exh.metadata.metadata.TsuminoSearchMetadata
import exh.metadata.metadata.TsuminoSearchMetadata.Companion.TAG_TYPE_DEFAULT
import exh.metadata.metadata.base.RaisedSearchMetadata.Companion.TAG_TYPE_VIRTUAL
import exh.metadata.metadata.base.RaisedTag
import exh.source.DelegatedHttpSource
import exh.ui.metadata.adapters.TsuminoDescription
import exh.util.dropBlank
import exh.util.trimAll
import exh.util.urlImportFetchSearchManga
@ -141,9 +138,4 @@ class Tsumino(delegate: HttpSource, val context: Context) :
val RATING_USERS_REGEX = "\\(([0-9].*) users".toRegex()
val RATING_FAVORITES_REGEX = "/ ([0-9].*) favs".toRegex()
}
@Composable
override fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
TsuminoDescription(state, openMetadataViewer)
}
}

View File

@ -13,10 +13,11 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.databinding.SourceFilterSheetBinding
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.online.BrowseSourceFilterHeader
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.ui.base.controller.BaseController
import eu.kanade.tachiyomi.widget.SimpleNavigationView
import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
import exh.md.MangaDexFabHeaderAdapter
import exh.savedsearches.EXHSavedSearch
import exh.source.getMainSource
@ -117,8 +118,12 @@ class SourceFilterSheet(
recycler.adapter = ConcatAdapter(
listOfNotNull(
controller?.let {
source?.getMainSource<BrowseSourceFilterHeader>()
?.getFilterHeader(it) { dismissSheet?.invoke() }
source?.getMainSource<MangaDex>()
?.let {
MangaDexFabHeaderAdapter(controller, it) {
dismissSheet?.invoke()
}
}
},
savedSearchesAdapter,
adapter,

View File

@ -46,7 +46,7 @@ open class AutoComplete(val filter: Filter.AutoComplete) : AbstractFlexibleItem<
// select from auto complete
holder.autoComplete.setOnItemClickListener { adapterView, _, chipPosition, _ ->
val name = adapterView.getItemAtPosition(chipPosition) as String
if (name !in if (filter.excludePrefix != null && name.startsWith(filter.excludePrefix)) filter.skipAutoFillTags.map { filter.excludePrefix + it } else filter.skipAutoFillTags) {
if (name !in if (filter.excludePrefix != null && name.startsWith(filter.excludePrefix!!)) filter.skipAutoFillTags.map { filter.excludePrefix + it } else filter.skipAutoFillTags) {
holder.autoComplete.text = null
addTag(name, holder)
}
@ -54,7 +54,7 @@ open class AutoComplete(val filter: Filter.AutoComplete) : AbstractFlexibleItem<
// done keyboard button is pressed
holder.autoComplete.setOnEditorActionListener { textView, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE && textView.text.toString() !in if (filter.excludePrefix != null && textView.text.toString().startsWith(filter.excludePrefix)) filter.skipAutoFillTags.map { filter.excludePrefix + it } else filter.skipAutoFillTags) {
if (actionId == EditorInfo.IME_ACTION_DONE && textView.text.toString() !in if (filter.excludePrefix != null && textView.text.toString().startsWith(filter.excludePrefix!!)) filter.skipAutoFillTags.map { filter.excludePrefix + it } else filter.skipAutoFillTags) {
textView.text = null
addTag(textView.text.toString(), holder)
return@setOnEditorActionListener true

View File

@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory
import eu.kanade.tachiyomi.util.preference.switchPreference
import eu.kanade.tachiyomi.util.preference.titleRes
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
import eu.kanade.tachiyomi.util.system.isTablet
import eu.kanade.tachiyomi.widget.preference.ThemesPreference
import java.util.Date

View File

@ -24,10 +24,8 @@ import android.util.TypedValue
import android.view.Display
import android.view.View
import android.view.WindowManager
import android.widget.Toast
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.annotation.StringRes
import androidx.appcompat.view.ContextThemeWrapper
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
@ -52,29 +50,6 @@ import kotlin.math.roundToInt
private const val TABLET_UI_MIN_SCREEN_WIDTH_DP = 720
/**
* Display a toast in this context.
*
* @param resource the text resource.
* @param duration the duration of the toast. Defaults to short.
*/
fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT, block: (Toast) -> Unit = {}): Toast {
return toast(getString(resource), duration, block)
}
/**
* Display a toast in this context.
*
* @param text the text to display.
* @param duration the duration of the toast. Defaults to short.
*/
fun Context.toast(text: String?, duration: Int = Toast.LENGTH_SHORT, block: (Toast) -> Unit = {}): Toast {
return Toast.makeText(applicationContext, text.orEmpty(), duration).also {
block(it)
it.show()
}
}
/**
* Copies a string to clipboard
*

View File

@ -0,0 +1,8 @@
package eu.kanade.tachiyomi.util.system
import android.os.Build
import com.google.android.material.color.DynamicColors
val DeviceUtil.isDynamicColorAvailable by lazy {
DynamicColors.isDynamicColorAvailable() || (DeviceUtil.isSamsung && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
}

View File

@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.databinding.DescriptionAdapterEhBinding
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil
import exh.metadata.bindDrawable
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
@Composable
fun EHentaiDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) {
@ -29,7 +29,7 @@ fun EHentaiDescription(state: MangaScreenState.Success, openMetadataViewer: () -
val binding = DescriptionAdapterEhBinding.bind(it)
binding.genre.text =
meta.genre?.let { MetadataUtil.getGenreAndColour(context, it) }
meta.genre?.let { MetadataUIUtil.getGenreAndColour(context, it) }
?.let {
binding.genre.setBackgroundColor(it.first)
it.second
@ -61,7 +61,7 @@ fun EHentaiDescription(state: MangaScreenState.Success, openMetadataViewer: () -
val ratingFloat = meta.averageRating?.toFloat()
binding.ratingBar.rating = ratingFloat ?: 0F
@SuppressLint("SetTextI18n")
binding.rating.text = (ratingFloat ?: 0F).toString() + " - " + MetadataUtil.getRatingString(context, ratingFloat?.times(2))
binding.rating.text = (ratingFloat ?: 0F).toString() + " - " + MetadataUIUtil.getRatingString(context, ratingFloat?.times(2))
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)

View File

@ -10,8 +10,8 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapter8mBinding
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.bindDrawable
import exh.metadata.metadata.EightMusesSearchMetadata
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
@Composable
fun EightMusesDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit) {

View File

@ -10,8 +10,8 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterHbBinding
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.bindDrawable
import exh.metadata.metadata.HBrowseSearchMetadata
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
@Composable
fun HBrowseDescription(state: MangaScreenState.Success, openMetadataViewer: () -> Unit) {

View File

@ -11,8 +11,8 @@ import eu.kanade.tachiyomi.databinding.DescriptionAdapterHiBinding
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil
import exh.metadata.bindDrawable
import exh.metadata.metadata.HitomiSearchMetadata
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
import java.util.Date
@Composable
@ -28,7 +28,7 @@ fun HitomiDescription(state: MangaScreenState.Success, openMetadataViewer: () ->
if (meta == null || meta !is HitomiSearchMetadata) return@AndroidView
val binding = DescriptionAdapterHiBinding.bind(it)
binding.genre.text = meta.genre?.let { MetadataUtil.getGenreAndColour(context, it) }?.let {
binding.genre.text = meta.genre?.let { MetadataUIUtil.getGenreAndColour(context, it) }?.let {
binding.genre.setBackgroundColor(it.first)
it.second
} ?: meta.genre ?: context.getString(R.string.unknown)

View File

@ -12,9 +12,9 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterMdBinding
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil.getRatingString
import exh.metadata.bindDrawable
import exh.metadata.metadata.MangaDexSearchMetadata
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
import exh.ui.metadata.adapters.MetadataUIUtil.getRatingString
import kotlin.math.round
@Composable

View File

@ -1,72 +1,17 @@
package exh.metadata
package exh.ui.metadata.adapters
import android.content.Context
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.annotation.FloatRange
import androidx.core.content.ContextCompat
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.R
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor
import exh.util.SourceTagsUtil
import java.text.SimpleDateFormat
import java.util.Locale
import kotlin.math.ln
import kotlin.math.pow
import kotlin.math.roundToInt
/**
* Metadata utils
*/
object MetadataUtil {
fun humanReadableByteCount(bytes: Long, si: Boolean): String {
val unit = if (si) 1000 else 1024
if (bytes < unit) return "$bytes B"
val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i"
return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre)
}
private const val KB_FACTOR: Long = 1000
private const val KIB_FACTOR: Long = 1024
private const val MB_FACTOR = 1000 * KB_FACTOR
private const val MIB_FACTOR = 1024 * KIB_FACTOR
private const val GB_FACTOR = 1000 * MB_FACTOR
private const val GIB_FACTOR = 1024 * MIB_FACTOR
fun parseHumanReadableByteCount(bytes: String): Double? {
val ret = bytes.substringBefore(' ').toDouble()
return when (bytes.substringAfter(' ')) {
"GB" -> ret * GB_FACTOR
"GiB" -> ret * GIB_FACTOR
"MB" -> ret * MB_FACTOR
"MiB" -> ret * MIB_FACTOR
"KB" -> ret * KB_FACTOR
"KiB" -> ret * KIB_FACTOR
else -> null
}
}
val ONGOING_SUFFIX = arrayOf(
"[ongoing]",
"(ongoing)",
"{ongoing}",
"<ongoing>",
"ongoing",
"[incomplete]",
"(incomplete)",
"{incomplete}",
"<incomplete>",
"incomplete",
"[wip]",
"(wip)",
"{wip}",
"<wip>",
"wip",
)
val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
object MetadataUIUtil {
fun getRatingString(context: Context, @FloatRange(from = 0.0, to = 10.0) rating: Float? = null) = when (rating?.roundToInt()) {
0 -> R.string.rating0
1 -> R.string.rating1
@ -103,12 +48,12 @@ object MetadataUtil {
}?.let { (genreColor, stringId) ->
genreColor.color to context.getString(stringId)
}
}
fun TextView.bindDrawable(context: Context, @DrawableRes drawable: Int) {
ContextCompat.getDrawable(context, drawable)?.apply {
setTint(context.getResourceColor(R.attr.colorAccent))
setBounds(0, 0, 20.dpToPx, 20.dpToPx)
setCompoundDrawables(this, null, null, null)
fun TextView.bindDrawable(context: Context, @DrawableRes drawable: Int) {
ContextCompat.getDrawable(context, drawable)?.apply {
setTint(context.getResourceColor(R.attr.colorAccent))
setBounds(0, 0, 20.dpToPx, 20.dpToPx)
setCompoundDrawables(this, null, null, null)
}
}
}

View File

@ -12,8 +12,8 @@ import eu.kanade.tachiyomi.databinding.DescriptionAdapterNhBinding
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil
import exh.metadata.bindDrawable
import exh.metadata.metadata.NHentaiSearchMetadata
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
import java.util.Date
@Composable
@ -32,7 +32,7 @@ fun NHentaiDescription(state: MangaScreenState.Success, openMetadataViewer: () -
binding.genre.text = meta.tags.filter { it.namespace == NHentaiSearchMetadata.NHENTAI_CATEGORIES_NAMESPACE }.let { tags ->
if (tags.isNotEmpty()) tags.joinToString(transform = { it.name }) else null
}.let { categoriesString ->
categoriesString?.let { MetadataUtil.getGenreAndColour(context, it) }?.let {
categoriesString?.let { MetadataUIUtil.getGenreAndColour(context, it) }?.let {
binding.genre.setBackgroundColor(it.first)
it.second
} ?: categoriesString ?: context.getString(R.string.unknown)

View File

@ -11,9 +11,8 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterPeBinding
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil
import exh.metadata.bindDrawable
import exh.metadata.metadata.PervEdenSearchMetadata
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
import java.util.Locale
import kotlin.math.round
@ -30,7 +29,7 @@ fun PervEdenDescription(state: MangaScreenState.Success, openMetadataViewer: ()
if (meta == null || meta !is PervEdenSearchMetadata) return@AndroidView
val binding = DescriptionAdapterPeBinding.bind(it)
binding.genre.text = meta.genre?.let { MetadataUtil.getGenreAndColour(context, it) }?.let {
binding.genre.text = meta.genre?.let { MetadataUIUtil.getGenreAndColour(context, it) }?.let {
binding.genre.setBackgroundColor(it.first)
it.second
} ?: meta.genre ?: context.getString(R.string.unknown)
@ -45,7 +44,7 @@ fun PervEdenDescription(state: MangaScreenState.Success, openMetadataViewer: ()
binding.ratingBar.rating = meta.rating ?: 0F
@SuppressLint("SetTextI18n")
binding.rating.text = (round((meta.rating ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUtil.getRatingString(context, meta.rating?.times(2))
binding.rating.text = (round((meta.rating ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUIUtil.getRatingString(context, meta.rating?.times(2))
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)

View File

@ -11,9 +11,8 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterPuBinding
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil
import exh.metadata.bindDrawable
import exh.metadata.metadata.PururinSearchMetadata
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
import kotlin.math.round
@Composable
@ -30,7 +29,7 @@ fun PururinDescription(state: MangaScreenState.Success, openMetadataViewer: () -
val binding = DescriptionAdapterPuBinding.bind(it)
binding.genre.text = meta.tags.find { it.namespace == PururinSearchMetadata.TAG_NAMESPACE_CATEGORY }.let { genre ->
genre?.let { MetadataUtil.getGenreAndColour(context, it.name) }?.let {
genre?.let { MetadataUIUtil.getGenreAndColour(context, it.name) }?.let {
binding.genre.setBackgroundColor(it.first)
it.second
} ?: genre?.name ?: context.getString(R.string.unknown)
@ -47,7 +46,7 @@ fun PururinDescription(state: MangaScreenState.Success, openMetadataViewer: () -
val ratingFloat = meta.averageRating?.toFloat()
binding.ratingBar.rating = ratingFloat ?: 0F
@SuppressLint("SetTextI18n")
binding.rating.text = (round((ratingFloat ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUtil.getRatingString(context, ratingFloat?.times(2))
binding.rating.text = (round((ratingFloat ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUIUtil.getRatingString(context, ratingFloat?.times(2))
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)

View File

@ -11,9 +11,8 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DescriptionAdapterTsBinding
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.system.copyToClipboard
import exh.metadata.MetadataUtil
import exh.metadata.bindDrawable
import exh.metadata.metadata.TsuminoSearchMetadata
import exh.ui.metadata.adapters.MetadataUIUtil.bindDrawable
import java.util.Date
import kotlin.math.round
@ -30,7 +29,7 @@ fun TsuminoDescription(state: MangaScreenState.Success, openMetadataViewer: () -
if (meta == null || meta !is TsuminoSearchMetadata) return@AndroidView
val binding = DescriptionAdapterTsBinding.bind(it)
binding.genre.text = meta.category?.let { MetadataUtil.getGenreAndColour(context, it) }?.let {
binding.genre.text = meta.category?.let { MetadataUIUtil.getGenreAndColour(context, it) }?.let {
binding.genre.setBackgroundColor(it.first)
it.second
} ?: meta.category ?: context.getString(R.string.unknown)
@ -47,7 +46,7 @@ fun TsuminoDescription(state: MangaScreenState.Success, openMetadataViewer: () -
binding.ratingBar.rating = meta.averageRating ?: 0F
@SuppressLint("SetTextI18n")
binding.rating.text = (round((meta.averageRating ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUtil.getRatingString(context, meta.averageRating?.times(2))
binding.rating.text = (round((meta.averageRating ?: 0F) * 100.0) / 100.0).toString() + " - " + MetadataUIUtil.getRatingString(context, meta.averageRating?.times(2))
binding.moreInfo.bindDrawable(context, R.drawable.ic_info_24dp)

View File

@ -34,12 +34,12 @@ object SourceTagsUtil {
}
if (parsed?.namespace != null) {
when (sourceId) {
in hitomiSourceIds -> wrapTagHitomi(parsed.namespace, parsed.name.substringBefore('|').trim())
in nHentaiSourceIds -> wrapTagNHentai(parsed.namespace, parsed.name.substringBefore('|').trim())
in hitomiSourceIds -> wrapTagHitomi(parsed.namespace!!, parsed.name.substringBefore('|').trim())
in nHentaiSourceIds -> wrapTagNHentai(parsed.namespace!!, parsed.name.substringBefore('|').trim())
in mangaDexSourceIds -> parsed.name
PURURIN_SOURCE_ID -> parsed.name.substringBefore('|').trim()
TSUMINO_SOURCE_ID -> wrapTagTsumino(parsed.namespace, parsed.name.substringBefore('|').trim())
else -> wrapTag(parsed.namespace, parsed.name.substringBefore('|').trim())
TSUMINO_SOURCE_ID -> wrapTagTsumino(parsed.namespace!!, parsed.name.substringBefore('|').trim())
else -> wrapTag(parsed.namespace!!, parsed.name.substringBefore('|').trim())
}
} else {
null

View File

@ -1,12 +0,0 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package exh.util
import java.util.Locale
fun String.capitalize(locale: Locale = Locale.getDefault()) =
replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() }

1
core/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

50
core/build.gradle.kts Normal file
View File

@ -0,0 +1,50 @@
plugins {
id("com.android.library")
kotlin("android")
kotlin("plugin.serialization")
}
android {
namespace = "eu.kanade.tachiyomi.core"
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
targetSdk = AndroidConfig.targetSdk
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
implementation(project(":i18n"))
api(libs.logcat)
api(libs.rxjava)
api(libs.okhttp.core)
api(libs.okhttp.logging)
api(libs.okhttp.dnsoverhttps)
api(libs.okio)
api(kotlinx.coroutines.core)
api(kotlinx.serialization.json)
api(libs.injekt.core)
api(libs.preferencektx)
implementation(androidx.corektx)
// SY -->
implementation(sylibs.xlog)
// SY <--
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@ -1,22 +1,22 @@
package eu.kanade.tachiyomi.network
import android.content.Context
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.i18n.BuildConfig
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
import eu.kanade.tachiyomi.network.interceptor.Http103Interceptor
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.util.concurrent.TimeUnit
/* SY --> */
open /* SY <-- */ class NetworkHelper(context: Context) {
private val preferences: PreferencesHelper by injectLazy()
// TODO: Abstract preferences similar to 1.x
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
private val cacheDir = File(context.cacheDir, "network_cache")
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
@ -46,7 +46,7 @@ open /* SY <-- */ class NetworkHelper(context: Context) {
builder.addNetworkInterceptor(httpLoggingInterceptor)
}
when (preferences.dohProvider()) {
when (preferences.getInt("doh_provider", -1)) {
PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
PREF_DOH_GOOGLE -> builder.dohGoogle()
PREF_DOH_ADGUARD -> builder.dohAdGuard()
@ -74,6 +74,6 @@ open /* SY <-- */ class NetworkHelper(context: Context) {
}
val defaultUserAgent by lazy {
preferences.defaultUserAgent().get()
preferences.getString("default_user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0")!!
}
}

View File

@ -10,7 +10,7 @@ import java.util.concurrent.TimeUnit.MINUTES
private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build()
private val DEFAULT_HEADERS = Headers.Builder().build()
private val DEFAULT_BODY: RequestBody = FormBody.Builder().build()
internal val CACHE_CONTROL_NO_STORE = CacheControl.Builder().noStore().build()
val CACHE_CONTROL_NO_STORE = CacheControl.Builder().noStore().build()
fun GET(
url: String,

View File

@ -5,7 +5,7 @@ import android.content.Context
import android.webkit.WebView
import android.widget.Toast
import androidx.core.content.ContextCompat
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.R
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
import eu.kanade.tachiyomi.util.system.isOutdated

View File

@ -5,7 +5,7 @@ import android.os.Build
import android.webkit.WebSettings
import android.webkit.WebView
import android.widget.Toast
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.R
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.DeviceUtil

View File

@ -5,6 +5,7 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import rx.Emitter
@ -20,6 +21,7 @@ import kotlin.coroutines.resumeWithException
suspend fun <T> Observable<T>.awaitSingle(): T = single().awaitOne()
@OptIn(InternalCoroutinesApi::class)
private suspend fun <T> Observable<T>.awaitOne(): T = suspendCancellableCoroutine { cont ->
cont.unsubscribeOnCancellation(
subscribe(

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.util.system
import android.annotation.SuppressLint
import android.os.Build
import com.google.android.material.color.DynamicColors
import logcat.LogPriority
object DeviceUtil {
@ -31,10 +30,6 @@ object DeviceUtil {
Build.MANUFACTURER.equals("samsung", ignoreCase = true)
}
val isDynamicColorAvailable by lazy {
DynamicColors.isDynamicColorAvailable() || (isSamsung && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
}
val invalidDefaultBrowsers = listOf("android", "com.huawei.android.internal.app")
@SuppressLint("PrivateApi")

View File

@ -0,0 +1,28 @@
package eu.kanade.tachiyomi.util.system
import android.content.Context
import android.widget.Toast
import androidx.annotation.StringRes
/**
* Display a toast in this context.
*
* @param resource the text resource.
* @param duration the duration of the toast. Defaults to short.
*/
fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT, block: (Toast) -> Unit = {}): Toast {
return toast(getString(resource), duration, block)
}
/**
* Display a toast in this context.
*
* @param text the text to display.
* @param duration the duration of the toast. Defaults to short.
*/
fun Context.toast(text: String?, duration: Int = Toast.LENGTH_SHORT, block: (Toast) -> Unit = {}): Toast {
return Toast.makeText(applicationContext, text.orEmpty(), duration).also {
block(it)
it.show()
}
}

View File

@ -3,8 +3,7 @@ package exh.log
import android.content.Context
import androidx.annotation.StringRes
import androidx.preference.PreferenceManager
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
import eu.kanade.tachiyomi.core.R
enum class EHLogLevel(@StringRes val nameRes: Int, @StringRes val description: Int) {
MINIMAL(R.string.log_minimal, R.string.log_minimal_desc),
@ -19,7 +18,7 @@ enum class EHLogLevel(@StringRes val nameRes: Int, @StringRes val description: I
fun init(context: Context) {
curLogLevel = PreferenceManager.getDefaultSharedPreferences(context)
.getInt(PreferenceKeys.eh_logLevel, 0)
.getInt("eh_log_level", 0) // todo
}
fun shouldLog(requiredLogLevel: EHLogLevel): Boolean {

View File

@ -1,5 +1,7 @@
package exh.util
import java.util.Locale
fun Collection<String>.trimAll() = map { it.trim() }
fun Collection<String>.dropBlank() = filter { it.isNotBlank() }
fun Collection<String>.dropEmpty() = filter { it.isNotEmpty() }
@ -13,3 +15,6 @@ fun String.removeArticles(): String {
fun String.trimOrNull() = trim().nullIfBlank()
fun String.nullIfBlank(): String? = ifBlank { null }
fun String.capitalize(locale: Locale = Locale.getDefault()) =
replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() }

View File

@ -40,3 +40,5 @@ dependencyResolutionManagement {
rootProject.name = "TachiyomiSY"
include(":app")
include(":i18n")
include(":source-api")
include(":core")

1
source-api/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,44 @@
plugins {
id("com.android.library")
kotlin("android")
kotlin("plugin.serialization")
}
android {
namespace = "eu.kanade.tachiyomi.source"
compileSdk = AndroidConfig.compileSdk
defaultConfig {
minSdk = AndroidConfig.minSdk
targetSdk = AndroidConfig.targetSdk
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
implementation(project(":core"))
api(kotlinx.serialization.json)
api(libs.rxjava)
api(libs.preferencektx)
api(libs.jsoup)
implementation(androidx.corektx)
// SY -->
implementation(project(":i18n"))
implementation(kotlinx.reflect)
// SY <--
}

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@ -1,16 +1,10 @@
package eu.kanade.tachiyomi.source
import android.graphics.drawable.Drawable
import eu.kanade.domain.source.model.SourceData
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.awaitSingle
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/**
* A basic interface for creating a source. It could be an online source, a local source, etc...
@ -88,53 +82,3 @@ interface Source {
return fetchPageList(chapter).awaitSingle()
}
}
fun Source.icon(): Drawable? = Injekt.get<ExtensionManager>().getAppIconForSource(this)
fun Source.getPreferenceKey(): String = "source_$id"
fun Source.toSourceData(): SourceData = SourceData(id = id, lang = lang, name = name)
fun Source.getNameForMangaInfo(mergeSources: List<Source>?): String {
val preferences = Injekt.get<PreferencesHelper>()
val enabledLanguages = preferences.enabledLanguages().get()
.filterNot { it in listOf("all", "other") }
val hasOneActiveLanguages = enabledLanguages.size == 1
val isInEnabledLanguages = lang in enabledLanguages
return when {
// SY -->
!mergeSources.isNullOrEmpty() -> getMergedSourcesString(
mergeSources,
enabledLanguages,
hasOneActiveLanguages,
)
// SY <--
// For edge cases where user disables a source they got manga of in their library.
hasOneActiveLanguages && !isInEnabledLanguages -> toString()
// Hide the language tag when only one language is used.
hasOneActiveLanguages && isInEnabledLanguages -> name
else -> toString()
}
}
// SY -->
private fun getMergedSourcesString(
mergeSources: List<Source>,
enabledLangs: List<String>,
onlyName: Boolean,
): String {
return if (onlyName) {
mergeSources.joinToString { source ->
if (source.lang !in enabledLangs) {
source.toString()
} else {
source.name
}
}
} else {
mergeSources.joinToString()
}
}
// SY <--
fun Source.isLocalOrStub(): Boolean = id == LocalSource.ID || this is SourceManager.StubSource

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.source.model
import data.Chapters
import java.io.Serializable
interface SChapter : Serializable {
@ -23,14 +22,6 @@ interface SChapter : Serializable {
scanlator = other.scanlator
}
fun copyFrom(other: Chapters) {
name = other.name
url = other.url
date_upload = other.date_upload
chapter_number = other.chapter_number
scanlator = other.scanlator
}
companion object {
fun create(): SChapter {
return SChapterImpl()

View File

@ -1,11 +1,5 @@
package eu.kanade.tachiyomi.source.model
import data.Mangas
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.download.DownloadManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.Serializable
interface SManga : Serializable {
@ -35,28 +29,17 @@ interface SManga : Serializable {
// SY -->
val originalTitle: String
get() = (this as? MangaImpl)?.ogTitle ?: title
val originalAuthor: String?
get() = (this as? MangaImpl)?.ogAuthor ?: author
val originalArtist: String?
get() = (this as? MangaImpl)?.ogArtist ?: artist
val originalDescription: String?
get() = (this as? MangaImpl)?.ogDesc ?: description
val originalGenre: String?
get() = (this as? MangaImpl)?.ogGenre ?: genre
val originalStatus: Int
get() = (this as? MangaImpl)?.ogStatus ?: status
// SY <--
fun copyFrom(other: SManga) {
// EXH -->
if (other.title.isNotBlank() && originalTitle != other.title) {
val oldTitle = originalTitle
title = other.originalTitle
val source = (this as? Manga)?.source
if (source != null) {
Injekt.get<DownloadManager>().renameMangaDir(oldTitle, other.originalTitle, source)
}
}
// EXH <--
@ -87,45 +70,6 @@ interface SManga : Serializable {
}
}
fun copyFrom(other: Mangas) {
// EXH -->
if (other.title.isNotBlank() && originalTitle != other.title) {
val oldTitle = originalTitle
title = other.title
val source = (this as? Manga)?.source
if (source != null) {
Injekt.get<DownloadManager>().renameMangaDir(oldTitle, other.title, source)
}
}
// EXH <--
if (other.author != null) {
author = other.author
}
if (other.artist != null) {
artist = other.artist
}
if (other.description != null) {
description = other.description
}
if (other.genre != null) {
genre = other.genre.joinToString(separator = ", ")
}
if (other.thumbnail_url != null) {
thumbnail_url = other.thumbnail_url
}
status = other.status.toInt()
if (!initialized) {
initialized = other.initialized
}
}
fun copy() = create().also {
it.url = url
// SY -->

View File

@ -21,4 +21,19 @@ class SMangaImpl : SManga {
override var thumbnail_url: String? = null
override var initialized: Boolean = false
// SY -->
override val originalTitle: String
get() = title
override val originalAuthor: String?
get() = author
override val originalArtist: String?
get() = artist
override val originalDescription: String?
get() = description
override val originalGenre: String?
get() = genre
override val originalStatus: Int
get() = status
// SY <--
}

View File

@ -1,10 +1,8 @@
package eu.kanade.tachiyomi.source.online
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import exh.md.utils.FollowStatus
import exh.metadata.metadata.base.RaisedSearchMetadata
interface FollowsSource : CatalogueSource {
@ -16,14 +14,4 @@ interface FollowsSource : CatalogueSource {
* @param SManga all smanga found for user
*/
suspend fun fetchAllFollows(): List<Pair<SManga, RaisedSearchMetadata>>
/**
* updates the follow status for a manga
*/
suspend fun updateFollowStatus(mangaID: String, followStatus: FollowStatus): Boolean
/**
* Get a MdList Track of the manga
*/
suspend fun fetchTrackingInfo(url: String): Track
}

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.source.online
import android.app.Application
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.AndroidCookieJar
import eu.kanade.tachiyomi.network.CACHE_CONTROL_NO_STORE
import eu.kanade.tachiyomi.network.GET
@ -15,7 +14,6 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import exh.log.maybeInjectEHLogger
import exh.patch.injectPatches
import exh.source.DelegatedHttpSource
import okhttp3.Headers
import okhttp3.OkHttpClient
@ -43,14 +41,14 @@ abstract class HttpSource : CatalogueSource {
override val client: OkHttpClient
get() = delegate?.networkHttpClient ?: network.client
.newBuilder()
.injectPatches { id }
//.injectPatches { id } todo
.maybeInjectEHLogger()
.build()
override val cloudflareClient: OkHttpClient
get() = delegate?.networkCloudflareClient ?: network.cloudflareClient
.newBuilder()
.injectPatches { id }
//.injectPatches { id } todo
.maybeInjectEHLogger()
.build()
@ -411,7 +409,7 @@ abstract class HttpSource : CatalogueSource {
// EXH -->
private var delegate: DelegatedHttpSource? = null
get() = if (Injekt.get<PreferencesHelper>().delegateSources().get()) {
get() = if (Injekt.get<NetworkHelper>().preferences.getBoolean("eh_delegate_sources", true)) { // todo
field
} else {
null

View File

@ -1,14 +1,10 @@
package eu.kanade.tachiyomi.source.online
import androidx.compose.runtime.Composable
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.InsertFlatMetadata
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.ui.manga.MangaScreenState
import eu.kanade.tachiyomi.util.lang.awaitSingle
import eu.kanade.tachiyomi.util.lang.runAsObservable
import exh.metadata.metadata.base.FlatMetadata
import exh.metadata.metadata.base.RaisedSearchMetadata
import rx.Completable
import rx.Single
@ -20,7 +16,16 @@ import kotlin.reflect.KClass
* LEWD!
*/
interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
val getManga: GetManga get() = Injekt.get()
interface GetMangaId {
suspend fun awaitId(url: String, sourceId: Long): Long?
}
interface InsertFlatMetadata {
suspend fun await(metadata: RaisedSearchMetadata)
}
interface GetFlatMetadataById {
suspend fun await(id: Long): FlatMetadata?
}
val getMangaId: GetMangaId get() = Injekt.get()
val insertFlatMetadata: InsertFlatMetadata get() = Injekt.get()
val getFlatMetadataById: GetFlatMetadataById get() = Injekt.get()
@ -111,8 +116,5 @@ interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
}
}
@Composable
fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit)
suspend fun SManga.id() = getManga.await(url, id)?.id
suspend fun SManga.id() = getMangaId.awaitId(url, id)
}

View File

@ -12,6 +12,7 @@ import org.jsoup.nodes.Element
/**
* A simple implementation for sources from a website using Jsoup, an HTML parser.
*/
@Suppress("unused")
abstract class ParsedHttpSource : HttpSource() {
/**

View File

@ -1,7 +1,7 @@
package exh.md.utils
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.R
enum class MangaDexRelation(@StringRes val resId: Int, val mdString: String?) {
SIMILAR(R.string.relation_similar, null),

View File

@ -0,0 +1,60 @@
package exh.metadata
import java.text.SimpleDateFormat
import java.util.Locale
import kotlin.math.ln
import kotlin.math.pow
/**
* Metadata utils
*/
object MetadataUtil {
fun humanReadableByteCount(bytes: Long, si: Boolean): String {
val unit = if (si) 1000 else 1024
if (bytes < unit) return "$bytes B"
val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt()
val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i"
return String.format("%.1f %sB", bytes / unit.toDouble().pow(exp.toDouble()), pre)
}
private const val KB_FACTOR: Long = 1000
private const val KIB_FACTOR: Long = 1024
private const val MB_FACTOR = 1000 * KB_FACTOR
private const val MIB_FACTOR = 1024 * KIB_FACTOR
private const val GB_FACTOR = 1000 * MB_FACTOR
private const val GIB_FACTOR = 1024 * MIB_FACTOR
fun parseHumanReadableByteCount(bytes: String): Double? {
val ret = bytes.substringBefore(' ').toDouble()
return when (bytes.substringAfter(' ')) {
"GB" -> ret * GB_FACTOR
"GiB" -> ret * GIB_FACTOR
"MB" -> ret * MB_FACTOR
"MiB" -> ret * MIB_FACTOR
"KB" -> ret * KB_FACTOR
"KiB" -> ret * KIB_FACTOR
else -> null
}
}
val ONGOING_SUFFIX = arrayOf(
"[ongoing]",
"(ongoing)",
"{ongoing}",
"<ongoing>",
"ongoing",
"[incomplete]",
"(incomplete)",
"{incomplete}",
"<incomplete>",
"incomplete",
"[wip]",
"(wip)",
"{wip}",
"<wip>",
"wip",
)
val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
}

View File

@ -2,8 +2,8 @@ package exh.metadata.metadata
import android.content.Context
import androidx.core.net.toUri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.R
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.copy
import exh.metadata.MetadataUtil
@ -53,7 +53,7 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
// No title bug?
val title = altTitle
?.takeIf { Injekt.get<PreferencesHelper>().useJapaneseTitle().get() }
?.takeIf { Injekt.get<NetworkHelper>().preferences.getBoolean("use_jp_title", false) } // todo
?: title
// Set artist (if we can find one)

Some files were not shown because too many files have changed in this diff Show More