mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Remove built-in official extension repo support
This commit is contained in:
		@@ -7,7 +7,7 @@ class CreateSourceRepo(private val preferences: SourcePreferences) {
 | 
			
		||||
 | 
			
		||||
    fun await(name: String): Result {
 | 
			
		||||
        // Do not allow invalid formats
 | 
			
		||||
        if (!name.matches(repoRegex) || name.startsWith(OFFICIAL_REPO_BASE_URL)) {
 | 
			
		||||
        if (!name.matches(repoRegex)) {
 | 
			
		||||
            return Result.InvalidUrl
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -22,5 +22,4 @@ class CreateSourceRepo(private val preferences: SourcePreferences) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const val OFFICIAL_REPO_BASE_URL = "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo"
 | 
			
		||||
private val repoRegex = """^https://.*/index\.min\.json$""".toRegex()
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.foundation.layout.size
 | 
			
		||||
import androidx.compose.foundation.lazy.items
 | 
			
		||||
import androidx.compose.material.icons.Icons
 | 
			
		||||
import androidx.compose.material.icons.outlined.History
 | 
			
		||||
import androidx.compose.material.icons.automirrored.outlined.Launch
 | 
			
		||||
import androidx.compose.material.icons.outlined.Settings
 | 
			
		||||
import androidx.compose.material3.AlertDialog
 | 
			
		||||
import androidx.compose.material3.Button
 | 
			
		||||
@@ -67,13 +67,23 @@ fun ExtensionDetailsScreen(
 | 
			
		||||
    navigateUp: () -> Unit,
 | 
			
		||||
    state: ExtensionDetailsScreenModel.State,
 | 
			
		||||
    onClickSourcePreferences: (sourceId: Long) -> Unit,
 | 
			
		||||
    onClickWhatsNew: () -> Unit,
 | 
			
		||||
    onClickEnableAll: () -> Unit,
 | 
			
		||||
    onClickDisableAll: () -> Unit,
 | 
			
		||||
    onClickClearCookies: () -> Unit,
 | 
			
		||||
    onClickUninstall: () -> Unit,
 | 
			
		||||
    onClickSource: (sourceId: Long) -> Unit,
 | 
			
		||||
) {
 | 
			
		||||
    val uriHandler = LocalUriHandler.current
 | 
			
		||||
    val url = remember(state.extension) {
 | 
			
		||||
        val regex = """https://raw.githubusercontent.com/(.+?)/(.+?)/.+""".toRegex()
 | 
			
		||||
        regex.find(state.extension?.repoUrl.orEmpty())
 | 
			
		||||
            ?.let {
 | 
			
		||||
                val (user, repo) = it.destructured
 | 
			
		||||
                "https://github.com/$user/$repo"
 | 
			
		||||
            }
 | 
			
		||||
            ?: state.extension?.repoUrl
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Scaffold(
 | 
			
		||||
        topBar = { scrollBehavior ->
 | 
			
		||||
            AppBar(
 | 
			
		||||
@@ -83,12 +93,14 @@ fun ExtensionDetailsScreen(
 | 
			
		||||
                    AppBarActions(
 | 
			
		||||
                        actions = persistentListOf<AppBar.AppBarAction>().builder()
 | 
			
		||||
                            .apply {
 | 
			
		||||
                                if (state.extension?.isUnofficial == false) {
 | 
			
		||||
                                if (url != null) {
 | 
			
		||||
                                    add(
 | 
			
		||||
                                        AppBar.Action(
 | 
			
		||||
                                            title = stringResource(MR.strings.whats_new),
 | 
			
		||||
                                            icon = Icons.Outlined.History,
 | 
			
		||||
                                            onClick = onClickWhatsNew,
 | 
			
		||||
                                            title = stringResource(MR.strings.action_open_repo),
 | 
			
		||||
                                            icon = Icons.AutoMirrored.Outlined.Launch,
 | 
			
		||||
                                            onClick = {
 | 
			
		||||
                                                uriHandler.openUri(url)
 | 
			
		||||
                                            },
 | 
			
		||||
                                        ),
 | 
			
		||||
                                    )
 | 
			
		||||
                                }
 | 
			
		||||
@@ -150,36 +162,10 @@ private fun ExtensionDetails(
 | 
			
		||||
    ScrollbarLazyColumn(
 | 
			
		||||
        contentPadding = contentPadding,
 | 
			
		||||
    ) {
 | 
			
		||||
        when {
 | 
			
		||||
            extension.isFromExternalRepo ->
 | 
			
		||||
                item {
 | 
			
		||||
                    val uriHandler = LocalUriHandler.current
 | 
			
		||||
                    val url = remember(extension) {
 | 
			
		||||
                        val regex = """https://raw.githubusercontent.com/(.+?)/(.+?)/.+""".toRegex()
 | 
			
		||||
                        regex.find(extension.repoUrl.orEmpty())
 | 
			
		||||
                            ?.let {
 | 
			
		||||
                                val (user, repo) = it.destructured
 | 
			
		||||
                                "https://github.com/$user/$repo"
 | 
			
		||||
                            }
 | 
			
		||||
                            ?: extension.repoUrl
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    WarningBanner(
 | 
			
		||||
                        MR.strings.repo_extension_message,
 | 
			
		||||
                        modifier = Modifier.clickable {
 | 
			
		||||
                            url ?: return@clickable
 | 
			
		||||
                            uriHandler.openUri(url)
 | 
			
		||||
                        },
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            extension.isUnofficial ->
 | 
			
		||||
                item {
 | 
			
		||||
                    WarningBanner(MR.strings.unofficial_extension_message)
 | 
			
		||||
                }
 | 
			
		||||
            extension.isObsolete ->
 | 
			
		||||
                item {
 | 
			
		||||
                    WarningBanner(MR.strings.obsolete_extension_message)
 | 
			
		||||
                }
 | 
			
		||||
        if (extension.isObsolete) {
 | 
			
		||||
            item {
 | 
			
		||||
                WarningBanner(MR.strings.obsolete_extension_message)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        item {
 | 
			
		||||
 
 | 
			
		||||
@@ -342,7 +342,6 @@ private fun ExtensionItemContent(
 | 
			
		||||
 | 
			
		||||
                val warning = when {
 | 
			
		||||
                    extension is Extension.Untrusted -> MR.strings.ext_untrusted
 | 
			
		||||
                    extension is Extension.Installed && extension.isUnofficial -> MR.strings.ext_unofficial
 | 
			
		||||
                    extension is Extension.Installed && extension.isObsolete -> MR.strings.ext_obsolete
 | 
			
		||||
                    extension.isNsfw -> MR.strings.ext_nsfw_short
 | 
			
		||||
                    else -> null
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ import eu.kanade.presentation.browse.components.BaseSourceItem
 | 
			
		||||
import eu.kanade.presentation.browse.components.SourceIcon
 | 
			
		||||
import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
 | 
			
		||||
import kotlinx.collections.immutable.ImmutableList
 | 
			
		||||
import tachiyomi.domain.source.model.Source
 | 
			
		||||
import tachiyomi.i18n.MR
 | 
			
		||||
import tachiyomi.presentation.core.components.Badge
 | 
			
		||||
@@ -75,7 +76,7 @@ fun MigrateSourceScreen(
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun MigrateSourceList(
 | 
			
		||||
    list: List<Pair<Source, Long>>,
 | 
			
		||||
    list: ImmutableList<Pair<Source, Long>>,
 | 
			
		||||
    contentPadding: PaddingValues,
 | 
			
		||||
    onClickItem: (Source) -> Unit,
 | 
			
		||||
    onLongClickItem: (Source) -> Unit,
 | 
			
		||||
 
 | 
			
		||||
@@ -178,7 +178,7 @@ class ExtensionManager(
 | 
			
		||||
            val pkgName = installedExt.pkgName
 | 
			
		||||
            val availableExt = availableExtensions.find { it.pkgName == pkgName }
 | 
			
		||||
 | 
			
		||||
            if (!installedExt.isUnofficial && availableExt == null && !installedExt.isObsolete) {
 | 
			
		||||
            if (availableExt == null && !installedExt.isObsolete) {
 | 
			
		||||
                mutInstalledExtensions[index] = installedExt.copy(isObsolete = true)
 | 
			
		||||
                changed = true
 | 
			
		||||
            } else if (availableExt != null) {
 | 
			
		||||
@@ -187,13 +187,11 @@ class ExtensionManager(
 | 
			
		||||
                if (installedExt.hasUpdate != hasUpdate) {
 | 
			
		||||
                    mutInstalledExtensions[index] = installedExt.copy(
 | 
			
		||||
                        hasUpdate = hasUpdate,
 | 
			
		||||
                        isFromExternalRepo = availableExt.isFromExternalRepo,
 | 
			
		||||
                        repoUrl = availableExt.repoUrl,
 | 
			
		||||
                    )
 | 
			
		||||
                    changed = true
 | 
			
		||||
                } else if (availableExt.isFromExternalRepo) {
 | 
			
		||||
                } else {
 | 
			
		||||
                    mutInstalledExtensions[index] = installedExt.copy(
 | 
			
		||||
                        isFromExternalRepo = true,
 | 
			
		||||
                        repoUrl = availableExt.repoUrl,
 | 
			
		||||
                    )
 | 
			
		||||
                    changed = true
 | 
			
		||||
@@ -363,7 +361,7 @@ class ExtensionManager(
 | 
			
		||||
 | 
			
		||||
    private fun Extension.Installed.updateExists(availableExtension: Extension.Available? = null): Boolean {
 | 
			
		||||
        val availableExt = availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName }
 | 
			
		||||
        if (isUnofficial || availableExt == null) return false
 | 
			
		||||
            ?: return false
 | 
			
		||||
 | 
			
		||||
        return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion)
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.api
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import eu.kanade.domain.source.interactor.OFFICIAL_REPO_BASE_URL
 | 
			
		||||
import eu.kanade.domain.source.service.SourcePreferences
 | 
			
		||||
import eu.kanade.tachiyomi.extension.ExtensionManager
 | 
			
		||||
import eu.kanade.tachiyomi.extension.model.Extension
 | 
			
		||||
@@ -36,14 +35,11 @@ internal class ExtensionApi {
 | 
			
		||||
 | 
			
		||||
    suspend fun findExtensions(): List<Extension.Available> {
 | 
			
		||||
        return withIOContext {
 | 
			
		||||
            val extensions = buildList {
 | 
			
		||||
                addAll(getExtensions(OFFICIAL_REPO_BASE_URL, true))
 | 
			
		||||
                sourcePreferences.extensionRepos().get().map { addAll(getExtensions(it, false)) }
 | 
			
		||||
            }
 | 
			
		||||
            val extensions = sourcePreferences.extensionRepos().get().flatMap { getExtensions(it) }
 | 
			
		||||
 | 
			
		||||
            // Sanity check - a small number of extensions probably means something broke
 | 
			
		||||
            // with the repo generator
 | 
			
		||||
            if (extensions.size < 50) {
 | 
			
		||||
            if (extensions.isEmpty()) {
 | 
			
		||||
                throw Exception()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -51,10 +47,7 @@ internal class ExtensionApi {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun getExtensions(
 | 
			
		||||
        repoBaseUrl: String,
 | 
			
		||||
        isOfficialRepo: Boolean,
 | 
			
		||||
    ): List<Extension.Available> {
 | 
			
		||||
    private suspend fun getExtensions(repoBaseUrl: String): List<Extension.Available> {
 | 
			
		||||
        return try {
 | 
			
		||||
            val response = networkService.client
 | 
			
		||||
                .newCall(GET("$repoBaseUrl/index.min.json"))
 | 
			
		||||
@@ -63,7 +56,7 @@ internal class ExtensionApi {
 | 
			
		||||
            with(json) {
 | 
			
		||||
                response
 | 
			
		||||
                    .parseAs<List<ExtensionJsonObject>>()
 | 
			
		||||
                    .toExtensions(repoBaseUrl, isRepoSource = !isOfficialRepo)
 | 
			
		||||
                    .toExtensions(repoBaseUrl)
 | 
			
		||||
            }
 | 
			
		||||
        } catch (e: Throwable) {
 | 
			
		||||
            logcat(LogPriority.ERROR, e) { "Failed to get extensions from $repoBaseUrl" }
 | 
			
		||||
@@ -98,7 +91,7 @@ internal class ExtensionApi {
 | 
			
		||||
            val availableExt = extensions.find { it.pkgName == pkgName } ?: continue
 | 
			
		||||
            val hasUpdatedVer = availableExt.versionCode > installedExt.versionCode
 | 
			
		||||
            val hasUpdatedLib = availableExt.libVersion > installedExt.libVersion
 | 
			
		||||
            val hasUpdate = installedExt.isUnofficial.not() && (hasUpdatedVer || hasUpdatedLib)
 | 
			
		||||
            val hasUpdate = hasUpdatedVer || hasUpdatedLib
 | 
			
		||||
            if (hasUpdate) {
 | 
			
		||||
                extensionsWithUpdate.add(installedExt)
 | 
			
		||||
            }
 | 
			
		||||
@@ -111,10 +104,7 @@ internal class ExtensionApi {
 | 
			
		||||
        return extensionsWithUpdate
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun List<ExtensionJsonObject>.toExtensions(
 | 
			
		||||
        repoUrl: String,
 | 
			
		||||
        isRepoSource: Boolean,
 | 
			
		||||
    ): List<Extension.Available> {
 | 
			
		||||
    private fun List<ExtensionJsonObject>.toExtensions(repoUrl: String): List<Extension.Available> {
 | 
			
		||||
        return this
 | 
			
		||||
            .filter {
 | 
			
		||||
                val libVersion = it.extractLibVersion()
 | 
			
		||||
@@ -133,7 +123,6 @@ internal class ExtensionApi {
 | 
			
		||||
                    apkName = it.apk,
 | 
			
		||||
                    iconUrl = "$repoUrl/icon/${it.pkg}.png",
 | 
			
		||||
                    repoUrl = repoUrl,
 | 
			
		||||
                    isFromExternalRepo = isRepoSource,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,10 +27,8 @@ sealed class Extension {
 | 
			
		||||
        val icon: Drawable?,
 | 
			
		||||
        val hasUpdate: Boolean = false,
 | 
			
		||||
        val isObsolete: Boolean = false,
 | 
			
		||||
        val isUnofficial: Boolean = false,
 | 
			
		||||
        val isShared: Boolean,
 | 
			
		||||
        val repoUrl: String? = null,
 | 
			
		||||
        val isFromExternalRepo: Boolean = false,
 | 
			
		||||
    ) : Extension()
 | 
			
		||||
 | 
			
		||||
    data class Available(
 | 
			
		||||
@@ -45,7 +43,6 @@ sealed class Extension {
 | 
			
		||||
        val apkName: String,
 | 
			
		||||
        val iconUrl: String,
 | 
			
		||||
        val repoUrl: String,
 | 
			
		||||
        val isFromExternalRepo: Boolean,
 | 
			
		||||
    ) : Extension() {
 | 
			
		||||
 | 
			
		||||
        data class Source(
 | 
			
		||||
 
 | 
			
		||||
@@ -59,9 +59,6 @@ internal object ExtensionLoader {
 | 
			
		||||
        PackageManager.GET_SIGNATURES or
 | 
			
		||||
        (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) PackageManager.GET_SIGNING_CERTIFICATES else 0)
 | 
			
		||||
 | 
			
		||||
    // inorichi's key
 | 
			
		||||
    private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
 | 
			
		||||
 | 
			
		||||
    private const val PRIVATE_EXTENSION_EXTENSION = "ext"
 | 
			
		||||
 | 
			
		||||
    private fun getPrivateExtensionDir(context: Context) = File(context.filesDir, "exts")
 | 
			
		||||
@@ -255,7 +252,7 @@ internal object ExtensionLoader {
 | 
			
		||||
        if (signatures.isNullOrEmpty()) {
 | 
			
		||||
            logcat(LogPriority.WARN) { "Package $pkgName isn't signed" }
 | 
			
		||||
            return LoadResult.Error
 | 
			
		||||
        } else if (!isTrusted(pkgInfo, signatures)) {
 | 
			
		||||
        } else if (!trustExtension.isTrusted(pkgInfo, signatures.last())) {
 | 
			
		||||
            val extension = Extension.Untrusted(
 | 
			
		||||
                extName,
 | 
			
		||||
                pkgName,
 | 
			
		||||
@@ -323,7 +320,6 @@ internal object ExtensionLoader {
 | 
			
		||||
            isNsfw = isNsfw,
 | 
			
		||||
            sources = sources,
 | 
			
		||||
            pkgFactory = appInfo.metaData.getString(METADATA_SOURCE_FACTORY),
 | 
			
		||||
            isUnofficial = !isOfficiallySigned(signatures),
 | 
			
		||||
            icon = appInfo.loadIcon(pkgManager),
 | 
			
		||||
            isShared = extensionInfo.isShared,
 | 
			
		||||
        )
 | 
			
		||||
@@ -383,18 +379,6 @@ internal object ExtensionLoader {
 | 
			
		||||
            ?.toList()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun isTrusted(pkgInfo: PackageInfo, signatures: List<String>): Boolean {
 | 
			
		||||
        if (officialSignature in signatures) {
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return trustExtension.isTrusted(pkgInfo, signatures.last())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun isOfficiallySigned(signatures: List<String>): Boolean {
 | 
			
		||||
        return signatures.all { it == officialSignature }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * On Android 13+ the ApplicationInfo generated by getPackageArchiveInfo doesn't
 | 
			
		||||
     * have sourceDir which breaks assets loading (used for getting icon here).
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.runtime.collectAsState
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.ui.platform.LocalContext
 | 
			
		||||
import androidx.compose.ui.platform.LocalUriHandler
 | 
			
		||||
import cafe.adriel.voyager.core.model.rememberScreenModel
 | 
			
		||||
import cafe.adriel.voyager.navigator.LocalNavigator
 | 
			
		||||
import cafe.adriel.voyager.navigator.currentOrThrow
 | 
			
		||||
@@ -30,13 +29,11 @@ data class ExtensionDetailsScreen(
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val navigator = LocalNavigator.currentOrThrow
 | 
			
		||||
        val uriHandler = LocalUriHandler.current
 | 
			
		||||
 | 
			
		||||
        ExtensionDetailsScreen(
 | 
			
		||||
            navigateUp = navigator::pop,
 | 
			
		||||
            state = state,
 | 
			
		||||
            onClickSourcePreferences = { navigator.push(SourcePreferencesScreen(it)) },
 | 
			
		||||
            onClickWhatsNew = { uriHandler.openUri(screenModel.getChangelogUrl()) },
 | 
			
		||||
            onClickEnableAll = { screenModel.toggleSources(true) },
 | 
			
		||||
            onClickDisableAll = { screenModel.toggleSources(false) },
 | 
			
		||||
            onClickClearCookies = screenModel::clearCookies,
 | 
			
		||||
 
 | 
			
		||||
@@ -29,9 +29,6 @@ import tachiyomi.core.util.system.logcat
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
 | 
			
		||||
private const val URL_EXTENSION_COMMITS =
 | 
			
		||||
    "https://github.com/tachiyomiorg/tachiyomi-extensions/commits/master"
 | 
			
		||||
 | 
			
		||||
class ExtensionDetailsScreenModel(
 | 
			
		||||
    pkgName: String,
 | 
			
		||||
    context: Context,
 | 
			
		||||
@@ -86,16 +83,6 @@ class ExtensionDetailsScreenModel(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getChangelogUrl(): String {
 | 
			
		||||
        val extension = state.value.extension ?: return ""
 | 
			
		||||
 | 
			
		||||
        val pkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.extension.")
 | 
			
		||||
        val pkgFactory = extension.pkgFactory
 | 
			
		||||
 | 
			
		||||
        // Falling back on GitHub commit history because there is no explicit changelog in extension
 | 
			
		||||
        return createUrl(URL_EXTENSION_COMMITS, pkgName, pkgFactory)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun clearCookies() {
 | 
			
		||||
        val extension = state.value.extension ?: return
 | 
			
		||||
 | 
			
		||||
@@ -131,22 +118,6 @@ class ExtensionDetailsScreenModel(
 | 
			
		||||
            ?.let { toggleSource.await(it, enable) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun createUrl(
 | 
			
		||||
        url: String,
 | 
			
		||||
        pkgName: String,
 | 
			
		||||
        pkgFactory: String?,
 | 
			
		||||
        path: String = "",
 | 
			
		||||
    ): String {
 | 
			
		||||
        return if (!pkgFactory.isNullOrEmpty()) {
 | 
			
		||||
            when (path.isEmpty()) {
 | 
			
		||||
                true -> "$url/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/$pkgFactory"
 | 
			
		||||
                else -> "$url/multisrc/overrides/$pkgFactory/" + (pkgName.split(".").lastOrNull() ?: "") + path
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            url + "/src/" + pkgName.replace(".", "/") + path
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Immutable
 | 
			
		||||
    data class State(
 | 
			
		||||
        val extension: Extension.Installed? = null,
 | 
			
		||||
 
 | 
			
		||||
@@ -56,12 +56,12 @@ class CrashLogUtil(
 | 
			
		||||
                val availableExtension = availableExtensions[it.pkgName]
 | 
			
		||||
                val hasUpdate = (availableExtension?.versionCode ?: 0) > it.versionCode
 | 
			
		||||
 | 
			
		||||
                if (!hasUpdate && !it.isObsolete && !it.isUnofficial) return@mapNotNull null
 | 
			
		||||
                if (!hasUpdate && !it.isObsolete) return@mapNotNull null
 | 
			
		||||
 | 
			
		||||
                """
 | 
			
		||||
                    - ${it.name}
 | 
			
		||||
                      Installed: ${it.versionName} / Available: ${availableExtension?.versionName ?: "?"}
 | 
			
		||||
                      Obsolete: ${it.isObsolete} / Unofficial: ${it.isUnofficial}
 | 
			
		||||
                      Obsolete: ${it.isObsolete}
 | 
			
		||||
                """.trimIndent()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -43,8 +43,6 @@ fun Long.toDateKey(): Date {
 | 
			
		||||
    return Date.from(instant.truncatedTo(ChronoUnit.DAYS))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private const val MILLISECONDS_IN_DAY = 86_400_000L
 | 
			
		||||
 | 
			
		||||
fun Date.toRelativeString(
 | 
			
		||||
    context: Context,
 | 
			
		||||
    relative: Boolean = true,
 | 
			
		||||
@@ -69,6 +67,8 @@ fun Date.toRelativeString(
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private const val MILLISECONDS_IN_DAY = 86_400_000L
 | 
			
		||||
 | 
			
		||||
private val Date.timeWithOffset: Long
 | 
			
		||||
    get() {
 | 
			
		||||
        return Calendar.getInstance().run {
 | 
			
		||||
@@ -78,6 +78,6 @@ private val Date.timeWithOffset: Long
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
fun Long.floorNearest(to: Long): Long {
 | 
			
		||||
private fun Long.floorNearest(to: Long): Long {
 | 
			
		||||
    return this.floorDiv(to) * to
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.source.Source
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.FilterList
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.HttpSource
 | 
			
		||||
import kotlinx.coroutines.flow.Flow
 | 
			
		||||
import kotlinx.coroutines.flow.combine
 | 
			
		||||
import kotlinx.coroutines.flow.map
 | 
			
		||||
import tachiyomi.data.DatabaseHandler
 | 
			
		||||
import tachiyomi.domain.source.model.SourceWithCount
 | 
			
		||||
@@ -38,18 +39,19 @@ class SourceRepositoryImpl(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getSourcesWithFavoriteCount(): Flow<List<Pair<DomainSource, Long>>> {
 | 
			
		||||
        val sourceIdWithFavoriteCount =
 | 
			
		||||
            handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() }
 | 
			
		||||
        return sourceIdWithFavoriteCount.map { sourceIdsWithCount ->
 | 
			
		||||
            sourceIdsWithCount
 | 
			
		||||
                .map { (sourceId, count) ->
 | 
			
		||||
        return combine(
 | 
			
		||||
            handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() },
 | 
			
		||||
            sourceManager.catalogueSources
 | 
			
		||||
        ) { sourceIdWithFavoriteCount, _ -> sourceIdWithFavoriteCount }
 | 
			
		||||
            .map {
 | 
			
		||||
                it.map { (sourceId, count) ->
 | 
			
		||||
                    val source = sourceManager.getOrStub(sourceId)
 | 
			
		||||
                    val domainSource = mapSourceToDomainSource(source).copy(
 | 
			
		||||
                        isStub = source is StubSource,
 | 
			
		||||
                    )
 | 
			
		||||
                    domainSource to count
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getSourcesWithNonLibraryManga(): Flow<List<SourceWithCount>> {
 | 
			
		||||
 
 | 
			
		||||
@@ -311,14 +311,12 @@
 | 
			
		||||
    <string name="ext_installing">Installing</string>
 | 
			
		||||
    <string name="ext_installed">Installed</string>
 | 
			
		||||
    <string name="ext_trust">Trust</string>
 | 
			
		||||
    <string name="ext_unofficial">Unofficial</string>
 | 
			
		||||
    <string name="ext_untrusted">Untrusted</string>
 | 
			
		||||
    <string name="ext_uninstall">Uninstall</string>
 | 
			
		||||
    <string name="ext_app_info">App info</string>
 | 
			
		||||
    <string name="untrusted_extension">Untrusted extension</string>
 | 
			
		||||
    <string name="untrusted_extension_message">This extension was signed by any unknown author and wasn\'t loaded.\n\nMalicious extensions can read any stored login credentials or execute arbitrary code.\n\nBy trusting this extension\'s certificate, you accept these risks.</string>
 | 
			
		||||
    <string name="untrusted_extension_message">Malicious extensions can read any stored login credentials or execute arbitrary code.\n\nBy trusting this extension, you accept these risks.</string>
 | 
			
		||||
    <string name="obsolete_extension_message">This extension is no longer available. It may not function properly and can cause issues with the app. Uninstalling it is recommended.</string>
 | 
			
		||||
    <string name="unofficial_extension_message">This extension is not from the official repo.</string>
 | 
			
		||||
    <string name="extension_api_error">Failed to fetch available extensions</string>
 | 
			
		||||
    <string name="ext_info_version">Version</string>
 | 
			
		||||
    <string name="ext_info_language">Language</string>
 | 
			
		||||
@@ -346,7 +344,7 @@
 | 
			
		||||
    <string name="action_delete_repo">Delete repo</string>
 | 
			
		||||
    <string name="invalid_repo_name">Invalid repo URL</string>
 | 
			
		||||
    <string name="delete_repo_confirmation">Do you wish to delete the repo \"%s\"?</string>
 | 
			
		||||
    <string name="repo_extension_message">This extension is from an external repo. Tap to view the repo.</string>
 | 
			
		||||
    <string name="action_open_repo">Open source repo</string>
 | 
			
		||||
 | 
			
		||||
      <!-- Reader section -->
 | 
			
		||||
    <string name="pref_fullscreen">Fullscreen</string>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user