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 f55532e12..fc99f4e66 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 fc2fab254..a39ebdc12 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 @@ -68,13 +68,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( @@ -84,12 +94,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) + }, ), ) } @@ -151,42 +163,16 @@ private fun ExtensionDetails( ScrollbarLazyColumn( contentPadding = contentPadding, ) { - when { - // SY --> - extension.isRedundant -> - item { - WarningBanner(SYMR.strings.redundant_extension_message) - } - // SY <-- - 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) - } + // SY --> + if (extension.isRedundant) { + item { + WarningBanner(SYMR.strings.redundant_extension_message) + } + // SY <-- + 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 cd533f7ea..551a42132 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionsScreen.kt @@ -340,7 +340,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 // SY --> extension is Extension.Installed && extension.isRedundant -> SYMR.strings.ext_redundant 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 c4b59f2c5..792e2e610 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt @@ -28,6 +28,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 @@ -83,7 +84,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 54975d1b3..2db20ec3d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -225,7 +225,7 @@ class ExtensionManager( val availableExt = _availableExtensionsFlow.value.find { it.pkgName == pkgName } // SY <-- - if (!installedExt.isUnofficial && availableExt == null && !installedExt.isObsolete) { + if (availableExt == null && !installedExt.isObsolete) { mutInstalledExtensions[index] = installedExt.copy(isObsolete = true) changed = true // SY --> @@ -239,13 +239,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 @@ -429,7 +427,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?.isRepoSource != true) || 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 a2e42c3ff..f902e86bb 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 @@ -37,14 +36,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() } @@ -52,10 +48,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")) @@ -64,7 +57,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" } @@ -106,7 +99,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) } @@ -119,10 +112,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() @@ -141,7 +131,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 853708979..2fa430ff0 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, // SY --> val isRedundant: Boolean = false, // SY <-- @@ -48,7 +46,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 728773ebe..6c765edf5 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 79af92328..4a4a78cde 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 c6e821bbd..8d8a9f607 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 6c74a5518..fbb99fa19 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt @@ -58,12 +58,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 e7f5c7ae2..c06be6def 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, @@ -68,6 +66,8 @@ fun Date.toRelativeString( } } +private const val MILLISECONDS_IN_DAY = 86_400_000L + private val Date.timeWithOffset: Long get() { return Calendar.getInstance().run { @@ -77,6 +77,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 f7bad7b8f..0d3a6cb08 100644 --- a/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/source/SourceRepositoryImpl.kt @@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource import exh.source.MERGED_SOURCE_ID import exh.source.isEhBasedSource 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 @@ -40,21 +41,22 @@ class SourceRepositoryImpl( } override fun getSourcesWithFavoriteCount(): Flow>> { - val sourceIdWithFavoriteCount = - handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() } - return sourceIdWithFavoriteCount.map { sourceIdsWithCount -> - sourceIdsWithCount + return combine( + handler.subscribeToList { mangasQueries.getSourceIdWithFavoriteCount() }, + sourceManager.catalogueSources, + ) { sourceIdWithFavoriteCount, _ -> sourceIdWithFavoriteCount } + .map { // SY --> - .filterNot { it.source == MERGED_SOURCE_ID } - // SY <-- - .map { (sourceId, count) -> - val source = sourceManager.getOrStub(sourceId) - val domainSource = mapSourceToDomainSource(source).copy( - isStub = source is StubSource, - ) - domainSource to count - } - } + it.filterNot { it.source == MERGED_SOURCE_ID } + // SY <-- + .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 950dc8005..92075a425 100755 --- 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