From 50b837c9762ea3bc04fb42175adb0659abfe4ff1 Mon Sep 17 00:00:00 2001 From: "Tran M. Cuong" Date: Sat, 24 Aug 2024 22:52:29 +0700 Subject: [PATCH] feat: enable/disable repo (#288) * Enable/Disable repo * update extensions list when refresh --- .../source/service/SourcePreferences.kt | 4 ++ .../screen/browse/ExtensionReposScreen.kt | 11 ++++ .../browse/ExtensionReposScreenModel.kt | 49 +++++++++++++++ .../components/ExtensionReposContent.kt | 63 ++++++++++++++++++- .../browse/components/ExtensionReposScreen.kt | 33 ++++++++++ .../tachiyomi/extension/ExtensionManager.kt | 25 +++++--- .../tachiyomi/extension/api/ExtensionApi.kt | 7 +++ .../moko-resources/base/strings.xml | 1 + 8 files changed, 183 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt index dae2b0ac2..37dfa6e67 100644 --- a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt @@ -55,6 +55,10 @@ class SourcePreferences( false, ) + // KMK --> + fun disabledRepos() = preferenceStore.getStringSet("disabled_repos", emptySet()) + // KMK <-- + // SY --> fun enableSourceBlacklist() = preferenceStore.getBoolean("eh_enable_source_blacklist", true) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/ExtensionReposScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/ExtensionReposScreen.kt index 03c9acd7c..638c52ff7 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/ExtensionReposScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/ExtensionReposScreen.kt @@ -17,6 +17,7 @@ import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.toast import kotlinx.collections.immutable.toImmutableSet import kotlinx.coroutines.flow.collectLatest +import tachiyomi.i18n.kmk.KMR import tachiyomi.presentation.core.screens.LoadingScreen class ExtensionReposScreen( @@ -47,6 +48,16 @@ class ExtensionReposScreen( onClickCreate = { screenModel.showDialog(RepoDialog.Create) }, onOpenWebsite = { context.openInBrowser(it.website) }, onClickDelete = { screenModel.showDialog(RepoDialog.Delete(it)) }, + // KMK --> + onClickEnable = { + screenModel.enableRepo(it) + context.toast(KMR.strings.extensions_page_need_refresh) + }, + onClickDisable = { + screenModel.disableRepo(it) + context.toast(KMR.strings.extensions_page_need_refresh) + }, + // KMK <-- onClickRefresh = { screenModel.refreshRepos() }, navigateUp = navigator::pop, ) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/ExtensionReposScreenModel.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/ExtensionReposScreenModel.kt index 4131bc6fd..8d37e26f1 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/ExtensionReposScreenModel.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/ExtensionReposScreenModel.kt @@ -4,10 +4,13 @@ import androidx.compose.runtime.Immutable import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope import dev.icerock.moko.resources.StringResource +import eu.kanade.domain.source.service.SourcePreferences import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.toImmutableSet import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import mihon.domain.extensionrepo.interactor.CreateExtensionRepo @@ -27,6 +30,9 @@ class ExtensionReposScreenModel( private val deleteExtensionRepo: DeleteExtensionRepo = Injekt.get(), private val replaceExtensionRepo: ReplaceExtensionRepo = Injekt.get(), private val updateExtensionRepo: UpdateExtensionRepo = Injekt.get(), + // KMK --> + private val sourcePreferences: SourcePreferences = Injekt.get(), + // KMK <-- ) : StateScreenModel(RepoScreenState.Loading) { private val _events: Channel = Channel(Int.MAX_VALUE) @@ -39,10 +45,26 @@ class ExtensionReposScreenModel( mutableState.update { RepoScreenState.Success( repos = repos.toImmutableSet(), + // KMK --> + disabledRepos = sourcePreferences.disabledRepos().get(), + // KMK <-- ) } } } + + // KMK --> + sourcePreferences.disabledRepos().changes() + .onEach { disabledRepos -> + mutableState.update { + when (it) { + is RepoScreenState.Success -> it.copy(disabledRepos = disabledRepos) + else -> it + } + } + } + .launchIn(screenModelScope) + // KMK <-- } /** @@ -91,11 +113,35 @@ class ExtensionReposScreenModel( * Deletes the given repo from the database */ fun deleteRepo(baseUrl: String) { + // KMK --> + // Remove repo from disabled list + enableRepo(baseUrl) + // KMK <-- screenModelScope.launchIO { deleteExtensionRepo.await(baseUrl) } } + // KMK --> + fun enableRepo(baseUrl: String) { + val disabledRepos = sourcePreferences.disabledRepos().get() + if (baseUrl in disabledRepos) { + sourcePreferences.disabledRepos().set( + disabledRepos.filterNot { it == baseUrl }.toSet(), + ) + } + } + + fun disableRepo(baseUrl: String) { + val disabledRepos = sourcePreferences.disabledRepos().get() + if (baseUrl !in disabledRepos) { + sourcePreferences.disabledRepos().set( + disabledRepos + baseUrl, + ) + } + } + // KMK <-- + fun showDialog(dialog: RepoDialog) { mutableState.update { when (it) { @@ -137,6 +183,9 @@ sealed class RepoScreenState { val repos: ImmutableSet, val oldRepos: ImmutableSet? = null, val dialog: RepoDialog? = null, + // KMK --> + val disabledRepos: Set = emptySet(), + // KMK <-- ) : RepoScreenState() { val isEmpty: Boolean diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposContent.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposContent.kt index e6b2a227f..112a2d2ab 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposContent.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposContent.kt @@ -12,17 +12,23 @@ import androidx.compose.material.icons.automirrored.outlined.Label import androidx.compose.material.icons.automirrored.outlined.OpenInNew import androidx.compose.material.icons.outlined.ContentCopy import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.Visibility +import androidx.compose.material.icons.outlined.VisibilityOff import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.tooling.preview.Preview import eu.kanade.tachiyomi.util.system.copyToClipboard import kotlinx.collections.immutable.ImmutableSet +import kotlinx.collections.immutable.persistentSetOf import mihon.domain.extensionrepo.model.ExtensionRepo import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.padding @@ -35,6 +41,11 @@ fun ExtensionReposContent( paddingValues: PaddingValues, onOpenWebsite: (ExtensionRepo) -> Unit, onClickDelete: (String) -> Unit, + // KMK --> + onClickEnable: (String) -> Unit, + onClickDisable: (String) -> Unit, + disabledRepos: Set, + // KMK <-- modifier: Modifier = Modifier, ) { LazyColumn( @@ -50,6 +61,11 @@ fun ExtensionReposContent( repo = it, onOpenWebsite = { onOpenWebsite(it) }, onDelete = { onClickDelete(it.baseUrl) }, + // KMK --> + onEnable = { onClickEnable(it.baseUrl) }, + onDisable = { onClickDisable(it.baseUrl) }, + isDisabled = it.baseUrl in disabledRepos, + // KMK <-- ) } } @@ -62,6 +78,11 @@ private fun ExtensionRepoListItem( onOpenWebsite: () -> Unit, onDelete: () -> Unit, modifier: Modifier = Modifier, + // KMK --> + isDisabled: Boolean, + onEnable: () -> Unit, + onDisable: () -> Unit, + // KMK <-- ) { val context = LocalContext.current @@ -78,11 +99,21 @@ private fun ExtensionRepoListItem( ), verticalAlignment = Alignment.CenterVertically, ) { - Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = null) + Icon( + imageVector = Icons.AutoMirrored.Outlined.Label, + contentDescription = null, + // KMK --> + tint = LocalContentColor.current.let { if (isDisabled) it.copy(alpha = 0.6f) else it }, + // KMK <-- + ) Text( text = repo.name, modifier = Modifier.padding(start = MaterialTheme.padding.medium), style = MaterialTheme.typography.titleMedium, + // KMK --> + color = LocalContentColor.current.let { if (isDisabled) it.copy(alpha = 0.6f) else it }, + textDecoration = TextDecoration.LineThrough.takeIf { isDisabled }, + // KMK <-- ) } @@ -109,6 +140,15 @@ private fun ExtensionRepoListItem( ) } + // KMK --> + IconButton(onClick = if (isDisabled) onEnable else onDisable) { + Icon( + imageVector = if (isDisabled) Icons.Outlined.VisibilityOff else Icons.Outlined.Visibility, + contentDescription = stringResource(MR.strings.action_disable), + ) + } + // KMK <-- + IconButton(onClick = onDelete) { Icon( imageVector = Icons.Outlined.Delete, @@ -118,3 +158,24 @@ private fun ExtensionRepoListItem( } } } + +// KMK --> +@Preview +@Composable +fun ExtensionReposContentPreview() { + val repos = persistentSetOf( + ExtensionRepo("url1", "Repo 1", "", "", "key1"), + ExtensionRepo("url2", "Repo 2", "", "", "key2"), + ) + ExtensionReposContent( + repos = repos, + lazyListState = LazyListState(), + paddingValues = PaddingValues(), + onOpenWebsite = {}, + onClickDelete = {}, + onClickEnable = {}, + onClickDisable = {}, + disabledRepos = setOf("url2"), + ) +} +// KMK <-- diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposScreen.kt index 37183a364..5200da6ff 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposScreen.kt @@ -40,6 +40,10 @@ fun ExtensionReposScreen( onClickCreate: () -> Unit, onOpenWebsite: (ExtensionRepo) -> Unit, onClickDelete: (String) -> Unit, + // KMK --> + onClickEnable: (String) -> Unit, + onClickDisable: (String) -> Unit, + // KMK <-- onClickRefresh: () -> Unit, navigateUp: () -> Unit, ) { @@ -93,6 +97,11 @@ fun ExtensionReposScreen( PaddingValues(horizontal = MaterialTheme.padding.medium), onOpenWebsite = onOpenWebsite, onClickDelete = onClickDelete, + // KMK --> + onClickEnable = onClickEnable, + onClickDisable = onClickDisable, + disabledRepos = state.disabledRepos, + // KMK <-- ) } } @@ -101,12 +110,36 @@ fun ExtensionReposScreen( @Preview @Composable private fun ExtensionReposScreenPreview() { + val state = RepoScreenState.Success( + repos = persistentSetOf( + ExtensionRepo("url1", "Repo 1", "", "", "key1"), + ExtensionRepo("url2", "Repo 2", "", "", "key2"), + ), + disabledRepos = setOf("url2"), + ) + ExtensionReposScreen( + state = state, + onClickCreate = { }, + onOpenWebsite = { }, + onClickDelete = { }, + onClickEnable = { }, + onClickDisable = { }, + onClickRefresh = { }, + navigateUp = { }, + ) +} + +@Preview +@Composable +private fun ExtensionReposScreenEmptyPreview() { val state = RepoScreenState.Success(repos = persistentSetOf()) ExtensionReposScreen( state = state, onClickCreate = { }, onOpenWebsite = { }, onClickDelete = { }, + onClickEnable = { }, + onClickDisable = { }, onClickRefresh = { }, navigateUp = { }, ) 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 daef404dc..de7008616 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -214,25 +214,32 @@ class ExtensionManager( * @param availableExtensions The list of extensions given by the [api]. */ private fun updatedInstalledExtensionsStatuses(availableExtensions: List) { - if (availableExtensions.isEmpty()) { - preferences.extensionUpdatesCount().set(0) - return - } - + // KMK --> + val noExtAvailable = availableExtensions.isEmpty() + // KMK <-- val installedExtensionsMap = installedExtensionMapFlow.value.toMutableMap() var changed = false for ((pkgName, extension) in installedExtensionsMap) { val availableExt = availableExtensions.find { it.pkgName == pkgName } - if (availableExt == null && !extension.isObsolete) { - installedExtensionsMap[pkgName] = extension.copy(isObsolete = true) - changed = true + // KMK --> + if (availableExt == null/* KMK --> && !extension.isObsolete // KMK <-- */) { + // Clear hasUpdate & set isObsolete + val isObsolete = !noExtAvailable && !extension.isObsolete + // KMK: installedExtensionsMap[pkgName] = extension.copy(isObsolete = true) + installedExtensionsMap[pkgName] = extension.copy( + isObsolete = isObsolete, + hasUpdate = false, + ) + // KMK: changed = true + changed = changed || isObsolete || noExtAvailable && (extension.isObsolete || extension.hasUpdate) + // KMK <-- // SY --> } else if (extension.isBlacklisted() && !extension.isRedundant) { installedExtensionsMap[pkgName] = extension.copy(isRedundant = true) changed = true // SY <-- - } else if (availableExt != null) { + } else /* KMK --> if (availableExt != null) // KMK <-- */ { val hasUpdate = extension.updateExists(availableExt) if (extension.hasUpdate != hasUpdate) { installedExtensionsMap[pkgName] = extension.copy( 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 5a5eb9e6e..788a77560 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 @@ -39,6 +39,7 @@ internal class ExtensionApi { private val sourcePreferences: SourcePreferences by injectLazy() // SY <-- + private val json: Json by injectLazy() private val lastExtCheck: Preference by lazy { @@ -46,8 +47,14 @@ internal class ExtensionApi { } suspend fun findExtensions(): List { + // KMK --> + val disabledRepos = sourcePreferences.disabledRepos().get() + // KMK <-- return withIOContext { getExtensionRepo.getAll() + // KMK --> + .filterNot { it.baseUrl in disabledRepos } + // KMK <-- .map { async { getExtensions(it) } } .awaitAll() .flatten() diff --git a/i18n-kmk/src/commonMain/moko-resources/base/strings.xml b/i18n-kmk/src/commonMain/moko-resources/base/strings.xml index 6feeca03f..1c43f655f 100644 --- a/i18n-kmk/src/commonMain/moko-resources/base/strings.xml +++ b/i18n-kmk/src/commonMain/moko-resources/base/strings.xml @@ -53,6 +53,7 @@ Unofficial This extension is not from the official repo. + Refresh extension page to update list. More extensions...