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