diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/CreateSourceRepo.kt b/app/src/main/java/eu/kanade/domain/source/interactor/CreateSourceRepo.kt index f55532e120..fc99f4e66c 100644 --- a/app/src/main/java/eu/kanade/domain/source/interactor/CreateSourceRepo.kt +++ b/app/src/main/java/eu/kanade/domain/source/interactor/CreateSourceRepo.kt @@ -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() diff --git a/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt index 213fe9dd2c..e55576db84 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt @@ -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().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 { diff --git a/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt index 5b641235d5..1b8b48a1ed 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt index e45ca51896..404d11476a 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt @@ -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>, + list: ImmutableList>, contentPadding: PaddingValues, onClickItem: (Source) -> Unit, onLongClickItem: (Source) -> Unit, diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index 156d881c8d..6b7594e86d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt index d0d493a97d..185f7cefd1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt @@ -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 { 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 { + private suspend fun getExtensions(repoBaseUrl: String): List { return try { val response = networkService.client .newCall(GET("$repoBaseUrl/index.min.json")) @@ -63,7 +56,7 @@ internal class ExtensionApi { with(json) { response .parseAs>() - .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.toExtensions( - repoUrl: String, - isRepoSource: Boolean, - ): List { + private fun List.toExtensions(repoUrl: String): List { 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, ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt index 4982ef6e3d..a8e80d0a52 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt @@ -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( diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt index 728773ebe4..6c765edf59 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt @@ -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): Boolean { - if (officialSignature in signatures) { - return true - } - - return trustExtension.isTrusted(pkgInfo, signatures.last()) - } - - private fun isOfficiallySigned(signatures: List): 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). diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreen.kt index 79af923284..4a4a78cdea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreen.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt index c6e821bbd1..8d8a9f607f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/ExtensionDetailsScreenModel.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt index 7f39823e71..4b2ef2ab16 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt @@ -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() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt index ab109c49b3..e7482165f9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/lang/DateExtensions.kt @@ -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 } diff --git a/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt index e8cf8f43c1..65bf33ac03 100644 --- a/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt @@ -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>> { - 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> { diff --git a/i18n/src/commonMain/resources/MR/base/strings.xml b/i18n/src/commonMain/resources/MR/base/strings.xml index 5da0b3a548..ccb9e8721d 100644 --- a/i18n/src/commonMain/resources/MR/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/base/strings.xml @@ -311,14 +311,12 @@ Installing Installed Trust - Unofficial Untrusted Uninstall App info Untrusted extension - 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. + Malicious extensions can read any stored login credentials or execute arbitrary code.\n\nBy trusting this extension, you accept these risks. This extension is no longer available. It may not function properly and can cause issues with the app. Uninstalling it is recommended. - This extension is not from the official repo. Failed to fetch available extensions Version Language @@ -346,7 +344,7 @@ Delete repo Invalid repo URL Do you wish to delete the repo \"%s\"? - This extension is from an external repo. Tap to view the repo. + Open source repo Fullscreen