This commit is contained in:
rebelonion 2024-01-18 01:09:30 -06:00
parent ff02280239
commit 664b5a4bdd
46 changed files with 317 additions and 190 deletions

View file

@ -180,7 +180,9 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
return view
}
@OptIn(UnstableApi::class) private fun grid(){
@OptIn(UnstableApi::class)
private fun grid() {
gridView.setOnItemClickListener { parent, view, position, id ->
// Get the OfflineAnimeModel that was clicked
val item = adapter.getItem(position) as OfflineAnimeModel
@ -211,7 +213,10 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
builder.setMessage("Are you sure you want to delete ${item.title}?")
builder.setPositiveButton("Yes") { _, _ ->
downloadManager.removeMedia(item.title, type)
val mediaIds = requireContext().getSharedPreferences(getString(R.string.anime_downloads), Context.MODE_PRIVATE)
val mediaIds = requireContext().getSharedPreferences(
getString(R.string.anime_downloads),
Context.MODE_PRIVATE
)
?.all?.filter { it.key.contains(item.title) }?.values ?: emptySet()
if (mediaIds.isEmpty()) {
snackString("No media found") // if this happens, terrible things have happened
@ -231,6 +236,7 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
true
}
}
override fun onSearchQuery(query: String) {
adapter.onSearchQuery(query)
}
@ -254,7 +260,8 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
}
}
val scrollTop = view.findViewById<CardView>(R.id.mangaPageScrollTop)
scrollTop.translationY = -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
scrollTop.translationY =
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
val visible = false
fun animate() {
@ -401,9 +408,13 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener {
mediaModel.status == currActivity()!!.getString(R.string.status_releasing)
val isUserScored = mediaModel.userScore != 0
val watchedEpisodes = (mediaModel.userProgress ?: "~").toString()
val totalEpisode = if (mediaModel.anime?.nextAiringEpisode != null) (mediaModel.anime.nextAiringEpisode.toString() + " | " + (mediaModel.anime.totalEpisodes ?: "~").toString()) else (mediaModel.anime?.totalEpisodes ?: "~").toString()
val totalEpisode =
if (mediaModel.anime?.nextAiringEpisode != null) (mediaModel.anime.nextAiringEpisode.toString() + " | " + (mediaModel.anime.totalEpisodes
?: "~").toString()) else (mediaModel.anime?.totalEpisodes ?: "~").toString()
val chapters = " Chapters"
val totalEpisodesList = if (mediaModel.anime?.nextAiringEpisode != null) (mediaModel.anime.nextAiringEpisode.toString()) else (mediaModel.anime?.totalEpisodes ?: "~").toString()
val totalEpisodesList =
if (mediaModel.anime?.nextAiringEpisode != null) (mediaModel.anime.nextAiringEpisode.toString()) else (mediaModel.anime?.totalEpisodes
?: "~").toString()
return OfflineAnimeModel(
title,
score,

View file

@ -1,6 +1,7 @@
package ani.dantotsu.download.anime
import android.net.Uri
data class OfflineAnimeModel(
val title: String,
val score: String,

View file

@ -171,10 +171,12 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
gridView.scheduleLayoutAnimation()
grid()
val total = view.findViewById<TextView>(R.id.total)
total.text = if (gridView.count > 0) "Manga and Novels (${gridView.count})" else "Empty List"
total.text =
if (gridView.count > 0) "Manga and Novels (${gridView.count})" else "Empty List"
return view
}
private fun grid() {
gridView.setOnItemClickListener { parent, view, position, id ->
// Get the OfflineMangaModel that was clicked
@ -220,6 +222,7 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
true
}
}
override fun onSearchQuery(query: String) {
adapter.onSearchQuery(query)
}
@ -243,7 +246,8 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener {
}
}
val scrollTop = view.findViewById<CardView>(R.id.mangaPageScrollTop)
scrollTop.translationY = -(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
scrollTop.translationY =
-(navBarHeight + bottomBar.height + bottomBar.marginBottom).toFloat()
val visible = false
fun animate() {

View file

@ -1,7 +1,6 @@
package ani.dantotsu.home
import android.animation.ObjectAnimator
import android.content.Context
import android.content.Intent
import android.graphics.drawable.Animatable
import android.os.Build

View file

@ -140,6 +140,7 @@ class MangaPageAdapter : RecyclerView.Adapter<MangaPageAdapter.MangaPageViewHold
fun updateHeight() {
trendingViewPager!!.updateLayoutParams { height += statusBarHeight }
}
fun updateTrending(adaptor: MediaAdaptor) {
binding.mangaTrendingProgressBar.visibility = View.GONE
binding.mangaTrendingViewPager.adapter = adaptor

View file

@ -1,24 +1,19 @@
package ani.dantotsu.media
import android.app.Activity
import android.content.Intent
import android.content.SharedPreferences
import android.os.Handler
import android.os.Looper
import androidx.annotation.OptIn
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.media3.common.util.UnstableApi
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.currActivity
import ani.dantotsu.currContext
import ani.dantotsu.loadData
import ani.dantotsu.logger
import ani.dantotsu.media.anime.Episode
import ani.dantotsu.media.anime.ExoplayerView
import ani.dantotsu.media.anime.SelectorDialogFragment
import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.others.AniSkip
@ -260,7 +255,12 @@ class MediaDetailsViewModel : ViewModel() {
}
media.selected = this.loadSelected(media)
val selector =
SelectorDialogFragment.newInstance(media.selected!!.server, launch, prevEp, isDownload)
SelectorDialogFragment.newInstance(
media.selected!!.server,
launch,
prevEp,
isDownload
)
selector.show(manager, "dialog")
}
}

View file

@ -166,11 +166,17 @@ class AnimeWatchFragment : Fragment() {
if (!loaded) {
model.watchSources = if (media.isAdult) HAnimeSources else AnimeSources
val offlineMode = model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex)
val offlineMode =
model.watchSources!!.isDownloadedSource(media.selected!!.sourceIndex)
headerAdapter = AnimeWatchAdapter(it, this, model.watchSources!!)
episodeAdapter =
EpisodeAdapter(style ?: uiSettings.animeDefaultView, media, this, offlineMode = offlineMode)
EpisodeAdapter(
style ?: uiSettings.animeDefaultView,
media,
this,
offlineMode = offlineMode
)
binding.animeSourceRecycler.adapter =
ConcatAdapter(headerAdapter, episodeAdapter)
@ -421,7 +427,10 @@ class AnimeWatchFragment : Fragment() {
fun onAnimeEpisodeStopDownloadClick(i: String) {
val cancelIntent = Intent().apply {
action = AnimeDownloaderService.ACTION_CANCEL_DOWNLOAD
putExtra(AnimeDownloaderService.EXTRA_TASK_NAME, AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i))
putExtra(
AnimeDownloaderService.EXTRA_TASK_NAME,
AnimeDownloaderService.AnimeDownloadTask.getTaskName(media.mainName(), i)
)
}
requireContext().sendBroadcast(cancelIntent)

View file

@ -75,7 +75,6 @@ import ani.dantotsu.media.MediaDetailsViewModel
import ani.dantotsu.media.SubtitleDownloader
import ani.dantotsu.others.AniSkip
import ani.dantotsu.others.AniSkip.getType
import ani.dantotsu.others.Download.download
import ani.dantotsu.others.LangSet
import ani.dantotsu.others.ResettableTimer
import ani.dantotsu.others.getSerialized

View file

@ -6,7 +6,6 @@ import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.util.TypedValue
import android.view.HapticFeedbackConstants
import android.view.LayoutInflater

View file

@ -7,9 +7,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import ani.dantotsu.App
import ani.dantotsu.R
import ani.dantotsu.currContext
import ani.dantotsu.databinding.FragmentOfflineBinding
import ani.dantotsu.isOnline
import ani.dantotsu.navBarHeight

View file

@ -169,7 +169,8 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
} catch (e: Exception) {
sourceLanguage = 0
extension.sources[sourceLanguage]
} as? AnimeHttpSource ?: (extension.sources[sourceLanguage] as? AnimeCatalogueSource ?: return emptyList())
} as? AnimeHttpSource ?: (extension.sources[sourceLanguage] as? AnimeCatalogueSource
?: return emptyList())
return try {
val res = source.fetchSearchAnime(1, query, source.getFilterList()).awaitSingle()
logger("query: $query")

View file

@ -13,7 +13,8 @@ class NotificationClickReceiver : BroadcastReceiver() {
context.getSharedPreferences("Dantotsu", Context.MODE_PRIVATE).edit()
.putBoolean("incognito", false)
.apply()
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancel(INCOGNITO_CHANNEL_ID)
}

View file

@ -4,7 +4,6 @@ import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
import android.os.Build
import android.view.Window
import android.view.WindowManager
import ani.dantotsu.R
@ -72,7 +71,7 @@ class ThemeManager(private val context: Activity) {
} else {
winParams.flags = winParams.flags and bits.inv()
}
win.setAttributes(winParams)
win.attributes = winParams
}
private fun applyDynamicColors(

View file

@ -20,7 +20,10 @@ class BasePreferences(
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", true)
fun deviceHasPip() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && context.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
fun deviceHasPip() =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && context.packageManager.hasSystemFeature(
PackageManager.FEATURE_PICTURE_IN_PICTURE
)
enum class ExtensionInstaller(val titleResId: String) {
LEGACY("Legacy"),

View file

@ -1,8 +1,8 @@
package eu.kanade.domain.base
import android.content.Context
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
import eu.kanade.domain.base.BasePreferences.ExtensionInstaller
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
import kotlinx.coroutines.CoroutineScope
import tachiyomi.core.preference.Preference
@ -19,10 +19,10 @@ class ExtensionInstallerPreference(
override fun key() = "extension_installer"
val entries get() = BasePreferences.ExtensionInstaller.values().run {
val entries
get() = ExtensionInstaller.values().run {
if (context.hasMiuiPackageInstaller) {
filter { it != BasePreferences.ExtensionInstaller.PACKAGEINSTALLER }
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
} else {
toList()
}
@ -39,9 +39,11 @@ class ExtensionInstallerPreference(
ExtensionInstaller.PACKAGEINSTALLER -> {
if (context.hasMiuiPackageInstaller) return ExtensionInstaller.LEGACY
}
ExtensionInstaller.SHIZUKU -> {
if (!context.isShizukuInstalled) return defaultValue()
}
else -> {}
}
return value

View file

@ -1,3 +1,4 @@
NOTICE
This software includes code modified from Aniyomi, available at https://github.com/aniyomiorg/aniyomi/.
This software includes code modified from Aniyomi, available
at https://github.com/aniyomiorg/aniyomi/.

View file

@ -38,9 +38,6 @@ open class Video(
@Transient
@Volatile
var status: State = State.QUEUE
set(value) {
field = value
}
@Transient
private val _progressFlow = MutableStateFlow(0)

View file

@ -69,7 +69,11 @@ sealed class AndroidPreference<T>(
key: String,
defaultValue: String,
) : AndroidPreference<String>(preferences, keyFlow, key, defaultValue) {
override fun read(preferences: SharedPreferences, key: String, defaultValue: String): String {
override fun read(
preferences: SharedPreferences,
key: String,
defaultValue: String
): String {
return try {
preferences.getString(key, defaultValue) ?: defaultValue
} catch (e: ClassCastException) {
@ -145,7 +149,11 @@ sealed class AndroidPreference<T>(
key: String,
defaultValue: Boolean,
) : AndroidPreference<Boolean>(preferences, keyFlow, key, defaultValue) {
override fun read(preferences: SharedPreferences, key: String, defaultValue: Boolean): Boolean {
override fun read(
preferences: SharedPreferences,
key: String,
defaultValue: Boolean
): Boolean {
return try {
preferences.getBoolean(key, defaultValue)
} catch (e: ClassCastException) {
@ -164,7 +172,11 @@ sealed class AndroidPreference<T>(
key: String,
defaultValue: Set<String>,
) : AndroidPreference<Set<String>>(preferences, keyFlow, key, defaultValue) {
override fun read(preferences: SharedPreferences, key: String, defaultValue: Set<String>): Set<String> {
override fun read(
preferences: SharedPreferences,
key: String,
defaultValue: Set<String>
): Set<String> {
return try {
preferences.getStringSet(key, defaultValue) ?: defaultValue
} catch (e: ClassCastException) {

View file

@ -68,7 +68,8 @@ class AndroidPreferenceStore(
@OptIn(ExperimentalCoroutinesApi::class)
private val SharedPreferences.keyFlow
get() = callbackFlow {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, key: String? -> trySend(key) }
val listener =
SharedPreferences.OnSharedPreferenceChangeListener { _, key: String? -> trySend(key) }
registerOnSharedPreferenceChangeListener(listener)
awaitClose {
unregisterOnSharedPreferenceChangeListener(listener)

View file

@ -1,37 +1,16 @@
package tachiyomi.core.util.system
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.BitmapRegionDecoder
import android.graphics.Color
import android.graphics.Matrix
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.webkit.MimeTypeMap
import androidx.annotation.ColorInt
import androidx.core.graphics.alpha
import androidx.core.graphics.applyCanvas
import androidx.core.graphics.blue
import androidx.core.graphics.createBitmap
import androidx.core.graphics.get
import androidx.core.graphics.green
import androidx.core.graphics.red
import com.hippo.unifile.UniFile
import logcat.LogPriority
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.net.URLConnection
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
object ImageUtil {

View file

@ -6,6 +6,7 @@ import android.content.Context
import android.content.Intent
import ani.dantotsu.MainActivity
import eu.kanade.tachiyomi.core.Constants
/**
* Global [BroadcastReceiver] that runs on UI thread
* Pending Broadcasts should be made from here.
@ -28,7 +29,12 @@ class NotificationReceiver {
action = Constants.SHORTCUT_EXTENSIONS
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
return PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
}

View file

@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallReceiver
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstaller
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader
import eu.kanade.tachiyomi.util.preference.plusAssign
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@ -57,20 +56,24 @@ class AnimeExtensionManager(
private val iconMap = mutableMapOf<String, Drawable>()
private val _installedAnimeExtensionsFlow = MutableStateFlow(emptyList<AnimeExtension.Installed>())
private val _installedAnimeExtensionsFlow =
MutableStateFlow(emptyList<AnimeExtension.Installed>())
val installedExtensionsFlow = _installedAnimeExtensionsFlow.asStateFlow()
private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet()
fun getAppIconForSource(sourceId: Long): Drawable? {
val pkgName = _installedAnimeExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName
val pkgName =
_installedAnimeExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName
if (pkgName != null) {
return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { context.packageManager.getApplicationIcon(pkgName) }
return iconMap[pkgName]
?: iconMap.getOrPut(pkgName) { context.packageManager.getApplicationIcon(pkgName) }
}
return null
}
private val _availableAnimeExtensionsFlow = MutableStateFlow(emptyList<AnimeExtension.Available>())
private val _availableAnimeExtensionsFlow =
MutableStateFlow(emptyList<AnimeExtension.Available>())
val availableExtensionsFlow = _availableAnimeExtensionsFlow.asStateFlow()
private var availableAnimeExtensionsSourcesData: Map<Long, AnimeSourceData> = emptyMap()
@ -84,7 +87,8 @@ class AnimeExtensionManager(
fun getSourceData(id: Long) = availableAnimeExtensionsSourcesData[id]
private val _untrustedAnimeExtensionsFlow = MutableStateFlow(emptyList<AnimeExtension.Untrusted>())
private val _untrustedAnimeExtensionsFlow =
MutableStateFlow(emptyList<AnimeExtension.Untrusted>())
val untrustedExtensionsFlow = _untrustedAnimeExtensionsFlow.asStateFlow()
init {
@ -213,7 +217,8 @@ class AnimeExtensionManager(
* @param extension The anime extension to be updated.
*/
fun updateExtension(extension: AnimeExtension.Installed): Observable<InstallStep> {
val availableExt = _availableAnimeExtensionsFlow.value.find { it.pkgName == extension.pkgName }
val availableExt =
_availableAnimeExtensionsFlow.value.find { it.pkgName == extension.pkgName }
?: return Observable.empty()
return installExtension(availableExt)
}
@ -251,20 +256,27 @@ class AnimeExtensionManager(
* @param signature The signature to whitelist.
*/
fun trustSignature(signature: String) {
val untrustedSignatures = _untrustedAnimeExtensionsFlow.value.map { it.signatureHash }.toSet()
val untrustedSignatures =
_untrustedAnimeExtensionsFlow.value.map { it.signatureHash }.toSet()
if (signature !in untrustedSignatures) return
AnimeExtensionLoader.trustedSignatures += signature
preferences.trustedSignatures() += signature
val nowTrustedAnimeExtensions = _untrustedAnimeExtensionsFlow.value.filter { it.signatureHash == signature }
val nowTrustedAnimeExtensions =
_untrustedAnimeExtensionsFlow.value.filter { it.signatureHash == signature }
_untrustedAnimeExtensionsFlow.value -= nowTrustedAnimeExtensions
val ctx = context
launchNow {
nowTrustedAnimeExtensions
.map { animeextension ->
async { AnimeExtensionLoader.loadExtensionFromPkgName(ctx, animeextension.pkgName) }
async {
AnimeExtensionLoader.loadExtensionFromPkgName(
ctx,
animeextension.pkgName
)
}
}
.map { it.await() }
.forEach { result ->
@ -307,11 +319,13 @@ class AnimeExtensionManager(
* @param pkgName The package name of the uninstalled application.
*/
private fun unregisterAnimeExtension(pkgName: String) {
val installedAnimeExtension = _installedAnimeExtensionsFlow.value.find { it.pkgName == pkgName }
val installedAnimeExtension =
_installedAnimeExtensionsFlow.value.find { it.pkgName == pkgName }
if (installedAnimeExtension != null) {
_installedAnimeExtensionsFlow.value -= installedAnimeExtension
}
val untrustedAnimeExtension = _untrustedAnimeExtensionsFlow.value.find { it.pkgName == pkgName }
val untrustedAnimeExtension =
_untrustedAnimeExtensionsFlow.value.find { it.pkgName == pkgName }
if (untrustedAnimeExtension != null) {
_untrustedAnimeExtensionsFlow.value -= untrustedAnimeExtension
}
@ -354,13 +368,15 @@ class AnimeExtensionManager(
}
private fun AnimeExtension.Installed.updateExists(availableAnimeExtension: AnimeExtension.Available? = null): Boolean {
val availableExt = availableAnimeExtension ?: _availableAnimeExtensionsFlow.value.find { it.pkgName == pkgName }
val availableExt = availableAnimeExtension
?: _availableAnimeExtensionsFlow.value.find { it.pkgName == pkgName }
if (isUnofficial || availableExt == null) return false
return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
}
private fun updatePendingUpdatesCount() {
preferences.animeExtensionUpdatesCount().set(_installedAnimeExtensionsFlow.value.count { it.hasUpdate })
preferences.animeExtensionUpdatesCount()
.set(_installedAnimeExtensionsFlow.value.count { it.hasUpdate })
}
}

View file

@ -73,7 +73,10 @@ internal class AnimeExtensionGithubApi {
}
}
suspend fun checkForUpdates(context: Context, fromAvailableExtensionList: Boolean = false): List<AnimeExtension.Installed>? {
suspend fun checkForUpdates(
context: Context,
fromAvailableExtensionList: Boolean = false
): List<AnimeExtension.Installed>? {
// Limit checks to once a day at most
if (fromAvailableExtensionList && Date().time < lastExtCheck.get() + 1.days.inWholeMilliseconds) {
return null
@ -161,8 +164,10 @@ private fun AnimeExtensionJsonObject.extractLibVersion(): Double {
return version.substringBeforeLast('.').toDouble()
}
private const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/aniyomiorg/aniyomi-extensions/repo/"
private const val FALLBACK_REPO_URL_PREFIX = "https://gcore.jsdelivr.net/gh/aniyomiorg/aniyomi-extensions@repo/"
private const val REPO_URL_PREFIX =
"https://raw.githubusercontent.com/aniyomiorg/aniyomi-extensions/repo/"
private const val FALLBACK_REPO_URL_PREFIX =
"https://gcore.jsdelivr.net/gh/aniyomiorg/aniyomi-extensions@repo/"
@Serializable
private data class AnimeExtensionJsonObject(

View file

@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.extension.anime.util
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.others.LangSet
import ani.dantotsu.themes.ThemeManager
import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller

View file

@ -62,6 +62,7 @@ internal class AnimeExtensionInstallReceiver(private val listener: Listener) :
}
}
}
Intent.ACTION_PACKAGE_REPLACED -> {
launchNow {
when (val result = getExtensionFromIntent(context, intent)) {
@ -72,6 +73,7 @@ internal class AnimeExtensionInstallReceiver(private val listener: Listener) :
}
}
}
Intent.ACTION_PACKAGE_REMOVED -> {
if (isReplacing(intent)) return

View file

@ -77,7 +77,11 @@ internal class AnimeExtensionInstaller(private val context: Context) {
val request = DownloadManager.Request(downloadUri)
.setTitle(extension.name)
.setMimeType(APK_MIME)
.setDestinationInExternalFilesDir(context, Environment.DIRECTORY_DOWNLOADS, downloadUri.lastPathSegment)
.setDestinationInExternalFilesDir(
context,
Environment.DIRECTORY_DOWNLOADS,
downloadUri.lastPathSegment
)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
val id = downloadManager.enqueue(request)
@ -144,6 +148,7 @@ internal class AnimeExtensionInstaller(private val context: Context) {
context.startActivity(intent)
}
else -> {
val intent =
AnimeExtensionInstallService.getIntent(context, downloadId, uri, installer)

View file

@ -73,7 +73,10 @@ internal class MangaExtensionGithubApi {
}
}
suspend fun checkForUpdates(context: Context, fromAvailableExtensionList: Boolean = false): List<MangaExtension.Installed>? {
suspend fun checkForUpdates(
context: Context,
fromAvailableExtensionList: Boolean = false
): List<MangaExtension.Installed>? {
// Limit checks to once a day at most
if (fromAvailableExtensionList && Date().time < lastExtCheck.get() + 1.days.inWholeMilliseconds) {
return null
@ -161,7 +164,8 @@ internal class MangaExtensionGithubApi {
}
private const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/keiyoushi/extensions/main/"
private const val FALLBACK_REPO_URL_PREFIX = "https://gcore.jsdelivr.net/gh/keiyoushi/extensions@main/"
private const val FALLBACK_REPO_URL_PREFIX =
"https://gcore.jsdelivr.net/gh/keiyoushi/extensions@main/"
@Serializable
private data class ExtensionJsonObject(

View file

@ -23,7 +23,10 @@ class PackageInstallerInstallerManga(private val service: Service) : InstallerMa
private val packageActionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE)) {
when (intent.getIntExtra(
PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE
)) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val userAction = intent.getParcelableExtraCompat<Intent>(Intent.EXTRA_INTENT)
if (userAction == null) {
@ -34,9 +37,11 @@ class PackageInstallerInstallerManga(private val service: Service) : InstallerMa
userAction.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
service.startActivity(userAction)
}
PackageInstaller.STATUS_FAILURE_ABORTED -> {
continueQueue(InstallStep.Idle)
}
PackageInstaller.STATUS_SUCCESS -> continueQueue(InstallStep.Installed)
else -> continueQueue(InstallStep.Error)
}
@ -52,7 +57,8 @@ class PackageInstallerInstallerManga(private val service: Service) : InstallerMa
super.processEntry(entry)
activeSession = null
try {
val installParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
val installParams =
PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
installParams.setRequireUserAction(PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED)
}
@ -60,7 +66,8 @@ class PackageInstallerInstallerManga(private val service: Service) : InstallerMa
val fileSize = service.getUriSize(entry.uri) ?: throw IllegalStateException()
installParams.setSize(fileSize)
val inputStream = service.contentResolver.openInputStream(entry.uri) ?: throw IllegalStateException()
val inputStream =
service.contentResolver.openInputStream(entry.uri) ?: throw IllegalStateException()
val session = packageInstaller.openSession(activeSession!!.second)
val outputStream = session.openWrite(entry.downloadId.toString(), 0, fileSize)
session.use {
@ -108,7 +115,12 @@ class PackageInstallerInstallerManga(private val service: Service) : InstallerMa
}
init {
ContextCompat.registerReceiver(service, packageActionReceiver, IntentFilter(INSTALL_ACTION), ContextCompat.RECEIVER_EXPORTED)
ContextCompat.registerReceiver(
service,
packageActionReceiver,
IntentFilter(INSTALL_ACTION),
ContextCompat.RECEIVER_EXPORTED
)
}
}

View file

@ -3,8 +3,8 @@ package eu.kanade.tachiyomi.extension.manga.util
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import ani.dantotsu.themes.ThemeManager
import ani.dantotsu.others.LangSet
import ani.dantotsu.themes.ThemeManager
import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
import eu.kanade.tachiyomi.util.system.hasMiuiPackageInstaller

View file

@ -62,6 +62,7 @@ internal class MangaExtensionInstallReceiver(private val listener: Listener) :
}
}
}
Intent.ACTION_PACKAGE_REPLACED -> {
launchNow {
when (val result = getExtensionFromIntent(context, intent)) {
@ -72,6 +73,7 @@ internal class MangaExtensionInstallReceiver(private val listener: Listener) :
}
}
}
Intent.ACTION_PACKAGE_REMOVED -> {
if (isReplacing(intent)) return

View file

@ -3,9 +3,7 @@ package eu.kanade.tachiyomi.extension.manga.util
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.net.Uri
import android.os.Build
import android.os.IBinder
@ -34,7 +32,11 @@ class MangaExtensionInstallService : Service() {
setProgress(100, 100, true)
}.build()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(Notifications.ID_EXTENSION_INSTALLER, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
startForeground(
Notifications.ID_EXTENSION_INSTALLER,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
)
} else {
startForeground(Notifications.ID_EXTENSION_INSTALLER, notification)
}
@ -53,7 +55,10 @@ class MangaExtensionInstallService : Service() {
if (installer == null) {
installer = when (installerUsed) {
BasePreferences.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstallerManga(this)
BasePreferences.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstallerManga(
this
)
else -> {
logcat(LogPriority.ERROR) { "Not implemented for installer $installerUsed" }
stopSelf()

View file

@ -41,15 +41,18 @@ internal object MangaExtensionLoader {
const val LIB_VERSION_MIN = 1.2
const val LIB_VERSION_MAX = 1.5
private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
private const val PACKAGE_FLAGS =
PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
// inorichi's key
private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
private const val officialSignature =
"7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
/**
* List of the trusted signatures.
*/
var trustedSignatures = mutableSetOf<String>() + preferences.trustedSignatures().get() + officialSignature
var trustedSignatures =
mutableSetOf<String>() + preferences.trustedSignatures().get() + officialSignature
/**
* Return a list of all the installed extensions initialized concurrently.
@ -105,7 +108,11 @@ internal object MangaExtensionLoader {
* @param pkgName The package name of the extension to load.
* @param pkgInfo The package info of the extension.
*/
private fun loadMangaExtension(context: Context, pkgName: String, pkgInfo: PackageInfo): MangaLoadResult {
private fun loadMangaExtension(
context: Context,
pkgName: String,
pkgInfo: PackageInfo
): MangaLoadResult {
val pkgManager = context.packageManager
val appInfo = try {
@ -116,7 +123,8 @@ internal object MangaExtensionLoader {
return MangaLoadResult.Error
}
val extName = pkgManager.getApplicationLabel(appInfo).toString().substringAfter("Tachiyomi: ")
val extName =
pkgManager.getApplicationLabel(appInfo).toString().substringAfter("Tachiyomi: ")
val versionName = pkgInfo.versionName
val versionCode = PackageInfoCompat.getLongVersionCode(pkgInfo)

View file

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.network
import android.content.Context
import android.os.Build
import ani.dantotsu.Mapper
import ani.dantotsu.defaultHeaders
import com.lagradost.nicehttp.Requests
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor
@ -31,8 +30,7 @@ class NetworkHelper(
CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider)
}
private fun baseClientBuilder(callTimout: Int = 2): OkHttpClient.Builder
{
private fun baseClientBuilder(callTimout: Int = 2): OkHttpClient.Builder {
val builder = OkHttpClient.Builder()
.cookieJar(cookieJar)
.connectTimeout(30, TimeUnit.SECONDS)
@ -68,7 +66,6 @@ class NetworkHelper(
}
val client by lazy { baseClientBuilder().cache(Cache(cacheDir, cacheSize)).build() }
val downloadClient by lazy { baseClientBuilder(20).build() }

View file

@ -1,7 +1,5 @@
package eu.kanade.tachiyomi.network
import eu.kanade.tachiyomi.network.ProgressListener
import eu.kanade.tachiyomi.network.ProgressResponseBody
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.DeserializationStrategy

View file

@ -9,7 +9,10 @@ import okio.Source
import okio.buffer
import java.io.IOException
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
class ProgressResponseBody(
private val responseBody: ResponseBody,
private val progressListener: ProgressListener
) : ResponseBody() {
private val bufferedSource: BufferedSource by lazy {
source(responseBody.source()).buffer()
@ -36,7 +39,11 @@ class ProgressResponseBody(private val responseBody: ResponseBody, private val p
val bytesRead = super.read(sink, byteCount)
// read() returns the number of bytes read, or -1 if this source is exhausted.
totalBytesRead += if (bytesRead != -1L) bytesRead else 0
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
progressListener.update(
totalBytesRead,
responseBody.contentLength(),
bytesRead == -1L
)
return bytesRead
}
}

View file

@ -95,7 +95,17 @@ private fun isRequestHeaderSafe(_name: String, _value: String): Boolean {
val name = _name.lowercase(Locale.ENGLISH)
val value = _value.lowercase(Locale.ENGLISH)
if (name in unsafeHeaderNames || name.startsWith("proxy-")) return false
if (name == "connection" && value == "upgrade") return false
return true
return !(name == "connection" && value == "upgrade")
}
private val unsafeHeaderNames = listOf("content-length", "host", "trailer", "te", "upgrade", "cookie2", "keep-alive", "transfer-encoding", "set-cookie")
private val unsafeHeaderNames = listOf(
"content-length",
"host",
"trailer",
"te",
"upgrade",
"cookie2",
"keep-alive",
"transfer-encoding",
"set-cookie"
)

View file

@ -33,7 +33,8 @@ interface MangaSource {
"Use the 1.x API instead",
ReplaceWith("getMangaDetails"),
)
fun fetchMangaDetails(manga: SManga): Observable<SManga> = throw IllegalStateException("Not used")
fun fetchMangaDetails(manga: SManga): Observable<SManga> =
throw IllegalStateException("Not used")
/**
* Returns an observable with all the available chapters for a manga.
@ -44,7 +45,8 @@ interface MangaSource {
"Use the 1.x API instead",
ReplaceWith("getChapterList"),
)
fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = throw IllegalStateException("Not used")
fun fetchChapterList(manga: SManga): Observable<List<SChapter>> =
throw IllegalStateException("Not used")
/**
* Returns an observable with the list of pages a chapter has. Pages should be returned

View file

@ -31,7 +31,8 @@ class AndroidAnimeSourceManager(
private val stubSourcesMap = ConcurrentHashMap<Long, StubAnimeSource>()
override val catalogueSources: Flow<List<AnimeCatalogueSource>> = sourcesMapFlow.map { it.values.filterIsInstance<AnimeCatalogueSource>() }
override val catalogueSources: Flow<List<AnimeCatalogueSource>> =
sourcesMapFlow.map { it.values.filterIsInstance<AnimeCatalogueSource>() }
init {
scope.launch {
@ -66,9 +67,11 @@ class AndroidAnimeSourceManager(
}
}
override fun getOnlineSources() = sourcesMapFlow.value.values.filterIsInstance<AnimeHttpSource>()
override fun getOnlineSources() =
sourcesMapFlow.value.values.filterIsInstance<AnimeHttpSource>()
override fun getCatalogueSources() = sourcesMapFlow.value.values.filterIsInstance<AnimeCatalogueSource>()
override fun getCatalogueSources() =
sourcesMapFlow.value.values.filterIsInstance<AnimeCatalogueSource>()
override fun getStubSources(): List<StubAnimeSource> {
val onlineSourceIds = getOnlineSources().map { it.id }

View file

@ -31,7 +31,8 @@ class AndroidMangaSourceManager(
private val stubSourcesMap = ConcurrentHashMap<Long, StubMangaSource>()
override val catalogueSources: Flow<List<CatalogueSource>> = sourcesMapFlow.map { it.values.filterIsInstance<CatalogueSource>() }
override val catalogueSources: Flow<List<CatalogueSource>> =
sourcesMapFlow.map { it.values.filterIsInstance<CatalogueSource>() }
init {
scope.launch {
@ -67,7 +68,8 @@ class AndroidMangaSourceManager(
override fun getOnlineSources() = sourcesMapFlow.value.values.filterIsInstance<HttpSource>()
override fun getCatalogueSources() = sourcesMapFlow.value.values.filterIsInstance<CatalogueSource>()
override fun getCatalogueSources() =
sourcesMapFlow.value.values.filterIsInstance<CatalogueSource>()
override fun getStubSources(): List<StubMangaSource> {
val onlineSourceIds = getOnlineSources().map { it.id }

View file

@ -3,7 +3,9 @@ package eu.kanade.tachiyomi.source.model
sealed class Filter<T>(val name: String, var state: T) {
open class Header(name: String) : Filter<Any>(name, 0)
open class Separator(name: String = "") : Filter<Any>(name, 0)
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state)
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) :
Filter<Int>(name, state)
abstract class Text(name: String, state: String = "") : Filter<String>(name, state)
abstract class CheckBox(name: String, state: Boolean = false) : Filter<Boolean>(name, state)
abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter<Int>(name, state) {

View file

@ -21,11 +21,10 @@ import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.util.lang.truncateCenter
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
import ani.dantotsu.toast
import com.hippo.unifile.UniFile
import java.io.File
import kotlin.math.roundToInt
@ -59,7 +58,8 @@ fun Context.copyToClipboard(label: String, content: String) {
* @param permission the permission to check.
* @return true if it has permissions.
*/
fun Context.hasPermission(permission: String) = PermissionChecker.checkSelfPermission(this, permission) == PermissionChecker.PERMISSION_GRANTED
fun Context.hasPermission(permission: String) =
PermissionChecker.checkSelfPermission(this, permission) == PermissionChecker.PERMISSION_GRANTED
/**
* Returns the color for the given attribute.
@ -67,7 +67,8 @@ fun Context.hasPermission(permission: String) = PermissionChecker.checkSelfPermi
* @param resource the attribute.
* @param alphaFactor the alpha number [0,1].
*/
@ColorInt fun Context.getResourceColor(@AttrRes resource: Int, alphaFactor: Float = 1f): Int {
@ColorInt
fun Context.getResourceColor(@AttrRes resource: Int, alphaFactor: Float = 1f): Int {
val typedArray = obtainStyledAttributes(intArrayOf(resource))
val color = typedArray.getColor(0, 0)
typedArray.recycle()
@ -80,7 +81,8 @@ fun Context.hasPermission(permission: String) = PermissionChecker.checkSelfPermi
return color
}
@ColorInt fun Context.getThemeColor(attr: Int): Int {
@ColorInt
fun Context.getThemeColor(attr: Int): Int {
val tv = TypedValue()
return if (this.theme.resolveAttribute(attr, tv, true)) {
if (tv.resourceId != 0) {
@ -137,7 +139,10 @@ fun Context.openInBrowser(uri: Uri, forceDefaultBrowser: Boolean = false) {
private fun Context.defaultBrowserPackageName(): String? {
val browserIntent = Intent(Intent.ACTION_VIEW, "http://".toUri())
val resolveInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
packageManager.resolveActivity(browserIntent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
packageManager.resolveActivity(
browserIntent,
PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong())
)
} else {
@Suppress("DEPRECATION")
packageManager.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
@ -184,7 +189,6 @@ val Context.hasMiuiPackageInstaller get() = isPackageInstalled("com.miui.package
val Context.isShizukuInstalled get() = false
fun Context.getApplicationIcon(pkgName: String): Drawable? {
return try {
packageManager.getApplicationIcon(pkgName)

View file

@ -16,6 +16,7 @@ fun Uri.toShareIntent(context: Context, type: String = "image/*", message: Strin
"http", "https" -> {
putExtra(Intent.EXTRA_TEXT, uri.toString())
}
"content" -> {
message?.let { putExtra(Intent.EXTRA_TEXT, it) }
putExtra(Intent.EXTRA_STREAM, uri)

View file

@ -10,7 +10,11 @@ import androidx.annotation.StringRes
* @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 {
fun Context.toast(
@StringRes resource: Int,
duration: Int = Toast.LENGTH_SHORT,
block: (Toast) -> Unit = {}
): Toast {
return toast(getString(resource), duration, block)
}
@ -20,7 +24,11 @@ fun Context.toast(@StringRes resource: Int, duration: Int = Toast.LENGTH_SHORT,
* @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 {
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

@ -30,7 +30,11 @@ object EpisodeRecognition {
*/
private val unwantedWhiteSpace = Regex("""\s(?=extra|special|omake)""")
fun parseEpisodeNumber(animeTitle: String, episodeName: String, episodeNumber: Float? = null): Float {
fun parseEpisodeNumber(
animeTitle: String,
episodeName: String,
episodeNumber: Float? = null
): Float {
// If episode number is known return.
if (episodeNumber != null && (episodeNumber == -2f || episodeNumber > -1f)) {
return episodeNumber

View file

@ -1,5 +1,6 @@
package tachiyomi.source.local.entries.anime
//import eu.kanade.tachiyomi.util.storage.toFFmpegString
import android.content.Context
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
import eu.kanade.tachiyomi.animesource.AnimeSource
@ -8,15 +9,11 @@ import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.storage.DiskUtil
//import eu.kanade.tachiyomi.util.storage.toFFmpegString
import kotlinx.serialization.json.Json
import rx.Observable
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.domain.entries.anime.model.Anime
import tachiyomi.source.local.filter.anime.AnimeOrderBy
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.util.concurrent.TimeUnit
@ -42,7 +39,11 @@ class LocalAnimeSource(
override fun fetchLatestUpdates(page: Int) = fetchSearchAnime(page, "", LATEST_FILTERS)
override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable<AnimesPage> {
override fun fetchSearchAnime(
page: Int,
query: String,
filters: AnimeFilterList
): Observable<AnimesPage> {
//return emptyObservable()
return Observable.just(AnimesPage(emptyList(), false))
}
@ -63,7 +64,8 @@ class LocalAnimeSource(
override fun getFilterList() = AnimeFilterList(AnimeOrderBy.Popular(context))
// Unused stuff
override suspend fun getVideoList(episode: SEpisode) = throw UnsupportedOperationException("Unused")
override suspend fun getVideoList(episode: SEpisode) =
throw UnsupportedOperationException("Unused")
companion object {
const val ID = 0L

View file

@ -37,7 +37,11 @@ class LocalMangaSource(
override fun fetchLatestUpdates(page: Int) = fetchSearchManga(page, "", LATEST_FILTERS)
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
override fun fetchSearchManga(
page: Int,
query: String,
filters: FilterList
): Observable<MangasPage> {
return Observable.just(MangasPage(emptyList(), false))
}
@ -55,7 +59,8 @@ class LocalMangaSource(
override fun getFilterList() = FilterList(MangaOrderBy.Popular(context))
// Unused stuff
override suspend fun getPageList(chapter: SChapter) = throw UnsupportedOperationException("Unused")
override suspend fun getPageList(chapter: SChapter) =
throw UnsupportedOperationException("Unused")
companion object {
const val ID = 0L