feat: enable/disable repo (#288)
* Enable/Disable repo * update extensions list when refresh
This commit is contained in:
parent
2536e5a77e
commit
50b837c976
8 changed files with 183 additions and 10 deletions
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>(RepoScreenState.Loading) {
|
||||
|
||||
private val _events: Channel<RepoEvent> = 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<ExtensionRepo>,
|
||||
val oldRepos: ImmutableSet<String>? = null,
|
||||
val dialog: RepoDialog? = null,
|
||||
// KMK -->
|
||||
val disabledRepos: Set<String> = emptySet(),
|
||||
// KMK <--
|
||||
) : RepoScreenState() {
|
||||
|
||||
val isEmpty: Boolean
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
// 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 <--
|
||||
|
|
|
|||
|
|
@ -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 = { },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -214,25 +214,32 @@ class ExtensionManager(
|
|||
* @param availableExtensions The list of extensions given by the [api].
|
||||
*/
|
||||
private fun updatedInstalledExtensionsStatuses(availableExtensions: List<Extension.Available>) {
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ internal class ExtensionApi {
|
|||
private val sourcePreferences: SourcePreferences by injectLazy()
|
||||
|
||||
// SY <--
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val lastExtCheck: Preference<Long> by lazy {
|
||||
|
|
@ -46,8 +47,14 @@ internal class ExtensionApi {
|
|||
}
|
||||
|
||||
suspend fun findExtensions(): List<Extension.Available> {
|
||||
// KMK -->
|
||||
val disabledRepos = sourcePreferences.disabledRepos().get()
|
||||
// KMK <--
|
||||
return withIOContext {
|
||||
getExtensionRepo.getAll()
|
||||
// KMK -->
|
||||
.filterNot { it.baseUrl in disabledRepos }
|
||||
// KMK <--
|
||||
.map { async { getExtensions(it) } }
|
||||
.awaitAll()
|
||||
.flatten()
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@
|
|||
<!-- Extension section -->
|
||||
<string name="ext_unofficial">Unofficial</string>
|
||||
<string name="unofficial_extension_message">This extension is not from the official repo.</string>
|
||||
<string name="extensions_page_need_refresh">Refresh extension page to update list.</string>
|
||||
<string name="extensions_page_more">More extensions...</string>
|
||||
|
||||
<!-- Extension repos -->
|
||||
|
|
|
|||
Loading…
Reference in a new issue