Remove built-in Komikku repo (#286)

* Add Help to Repo EmptyScreen

* Migration: insert Official Repo for old users

* button to auto fill official KMK extension repo

* 'More extensions' button to go to Repo page

* Remove default official repo

* fix spotless
This commit is contained in:
Tran M. Cuong 2024-08-24 22:30:13 +07:00 committed by Cuong-Tran
parent 3b8752930e
commit 2536e5a77e
No known key found for this signature in database
GPG key ID: 733AA7624B9315C2
12 changed files with 168 additions and 41 deletions

View file

@ -40,6 +40,7 @@ class SourcePreferences(
fun hideInLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
@Deprecated("Use ExtensionRepoRepository instead", replaceWith = ReplaceWith("ExtensionRepoRepository.getAll()"))
fun extensionRepos() = preferenceStore.getStringSet("extension_repos", emptySet())
fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)

View file

@ -157,6 +157,9 @@ private fun ExtensionContent(
val context = LocalContext.current
var trustState by remember { mutableStateOf<Extension.Untrusted?>(null) }
val installGranted = rememberRequestPackageInstallsPermissionState(initialValue = true)
// KMK -->
val navigator = LocalNavigator.current
// KMK <--
FastScrollLazyColumn(
contentPadding = contentPadding + topSmallPaddingValues,
@ -180,19 +183,36 @@ private fun ExtensionContent(
when (header) {
is ExtensionUiModel.Header.Resource -> {
val action: @Composable RowScope.() -> Unit =
if (header.textRes == MR.strings.ext_updates_pending) {
{
Button(onClick = { onClickUpdateAll() }) {
Text(
text = stringResource(MR.strings.ext_update_all),
style = LocalTextStyle.current.copy(
color = MaterialTheme.colorScheme.onPrimary,
),
)
when (header.textRes) {
MR.strings.ext_updates_pending -> {
{
Button(onClick = { onClickUpdateAll() }) {
Text(
text = stringResource(MR.strings.ext_update_all),
style = LocalTextStyle.current.copy(
color = MaterialTheme.colorScheme.onPrimary,
),
)
}
}
}
} else {
{}
// KMK -->
KMR.strings.extensions_page_more -> {
{
Button(onClick = { navigator?.push(ExtensionReposScreen()) }) {
Text(
text = stringResource(MR.strings.action_add_repo),
style = LocalTextStyle.current.copy(
color = MaterialTheme.colorScheme.onPrimary,
),
)
}
}
}
// KMK <--
else -> {
{}
}
}
ExtensionHeader(
textRes = header.textRes,

View file

@ -1,6 +1,9 @@
package eu.kanade.presentation.more.settings.screen.browse.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.OutlinedTextField
@ -12,12 +15,16 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.coroutines.delay
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo.Companion.OFFICIAL_REPO_BASE_URL
import mihon.domain.extensionrepo.model.ExtensionRepo
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
@ -37,19 +44,37 @@ fun ExtensionRepoCreateDialog(
AlertDialog(
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(
enabled = name.isNotEmpty() && !nameAlreadyExists,
onClick = {
onCreate(name)
onDismissRequest()
},
// KMK -->
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween, // Distribute space between items
verticalAlignment = Alignment.CenterVertically, // Align items vertically
) {
Text(text = stringResource(MR.strings.action_add))
}
},
dismissButton = {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_cancel))
TextButton(
onClick = {
name = "$OFFICIAL_REPO_BASE_URL/index.min.json"
},
) {
Text(text = stringResource(MR.strings.label_default))
}
Row(
// Group the right-aligned elements
verticalAlignment = Alignment.CenterVertically,
) {
TextButton(onClick = onDismissRequest) {
Text(text = stringResource(MR.strings.action_cancel))
}
// KMK <--
TextButton(
enabled = name.isNotEmpty() && !nameAlreadyExists,
onClick = {
onCreate(name)
onDismissRequest()
},
) {
Text(text = stringResource(MR.strings.action_add))
}
}
}
},
title = {
@ -90,6 +115,14 @@ fun ExtensionRepoCreateDialog(
}
}
// KMK -->
@Preview(showBackground = true)
@Composable
fun ExtensionRepoCreateDialogPreview() {
ExtensionRepoCreateDialog({ }, { }, persistentSetOf())
}
// KMK <--
@Composable
fun ExtensionRepoDeleteDialog(
onDismissRequest: () -> Unit,

View file

@ -3,18 +3,28 @@
package eu.kanade.presentation.more.settings.screen.browse.components
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Help
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.more.settings.screen.browse.RepoScreenState
import eu.kanade.tachiyomi.util.system.openInBrowser
import kotlinx.collections.immutable.persistentSetOf
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo.Companion.OFFICIAL_REPO_WEBSITE
import mihon.domain.extensionrepo.model.ExtensionRepo
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
@ -58,9 +68,20 @@ fun ExtensionReposScreen(
},
) { paddingValues ->
if (state.isEmpty) {
val context = LocalContext.current
EmptyScreen(
MR.strings.information_empty_repos,
modifier = Modifier.padding(paddingValues),
help = {
TextButton(
onClick = { context.openInBrowser(OFFICIAL_REPO_WEBSITE) },
modifier = Modifier.padding(top = MaterialTheme.padding.small),
) {
Icon(imageVector = Icons.AutoMirrored.Outlined.Help, contentDescription = null)
Spacer(modifier = Modifier.width(MaterialTheme.padding.extraSmall))
Text(text = stringResource(MR.strings.label_help))
}
},
)
return@Scaffold
}
@ -75,3 +96,19 @@ fun ExtensionReposScreen(
)
}
}
// KMK -->
@Preview
@Composable
private fun ExtensionReposScreenPreview() {
val state = RepoScreenState.Success(repos = persistentSetOf())
ExtensionReposScreen(
state = state,
onClickCreate = { },
onOpenWebsite = { },
onClickDelete = { },
onClickRefresh = { },
navigateUp = { },
)
}
// KMK <--

View file

@ -16,8 +16,6 @@ import kotlinx.coroutines.awaitAll
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import logcat.LogPriority
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo.Companion.OFFICIAL_REPO_BASE_URL
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo.Companion.OFFICIAL_REPO_SIGNATURE
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
import mihon.domain.extensionrepo.model.ExtensionRepo
@ -48,22 +46,11 @@ internal class ExtensionApi {
}
suspend fun findExtensions(): List<Extension.Available> {
val officialRepo = ExtensionRepo(
baseUrl = OFFICIAL_REPO_BASE_URL,
name = "Komikku Official Extensions Repo",
shortName = "Komikku Official",
website = "https://komikku-app.github.io",
signingKeyFingerprint = OFFICIAL_REPO_SIGNATURE,
)
return withIOContext {
buildList {
// Combine built-in OFFICIAL repo's extensions list with repo's list
addAll(getExtensions(officialRepo))
getExtensionRepo.getAll()
.map { async { getExtensions(it) } }
.awaitAll()
.flatten()
}
getExtensionRepo.getAll()
.map { async { getExtensions(it) } }
.awaitAll()
.flatten()
}
}

View file

@ -30,6 +30,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.i18n.MR
import tachiyomi.i18n.kmk.KMR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Duration.Companion.seconds
@ -130,6 +131,11 @@ class ExtensionsScreenModel(
if (languagesWithExtensions.isNotEmpty()) {
itemsGroups.putAll(languagesWithExtensions)
}
// KMK -->
if (_available.isEmpty()) {
itemsGroups[ExtensionUiModel.Header.Resource(KMR.strings.extensions_page_more)] = emptyList()
}
// KMK <--
itemsGroups
}

View file

@ -1,6 +1,7 @@
package mihon.core.migration
interface Migration {
/** Begin version of this Migration */
val version: Float
suspend operator fun invoke(migrationContext: MigrationContext): Boolean

View file

@ -45,4 +45,7 @@ val migrations: List<Migration>
MoveCacheToDiskSettingMigration(),
MoveEncryptionSettingsToAppStateMigration(),
TrustExtensionRepositoryMigration(),
// KMK -->
OfficialExtensionRepositoryMigration(),
// KMK <--
)

View file

@ -0,0 +1,32 @@
package mihon.core.migration.migrations
import logcat.LogPriority
import mihon.core.migration.Migration
import mihon.core.migration.MigrationContext
import mihon.domain.extensionrepo.exception.SaveExtensionRepoException
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.logcat
class OfficialExtensionRepositoryMigration : Migration {
override val version: Float = 70f
override suspend fun invoke(migrationContext: MigrationContext): Boolean = withIOContext {
val extensionRepositoryRepository =
migrationContext.get<ExtensionRepoRepository>() ?: return@withIOContext false
try {
extensionRepositoryRepository.upsertRepo(
baseUrl = CreateExtensionRepo.OFFICIAL_REPO_BASE_URL,
name = "Komikku Official",
shortName = "Komikku",
website = "https://komikku-app.github.io",
signingKeyFingerprint = CreateExtensionRepo.OFFICIAL_REPO_SIGNATURE,
)
} catch (e: SaveExtensionRepoException) {
logcat(LogPriority.ERROR, e) { "Error inserting Official Extension Repo" }
return@withIOContext false
}
return@withIOContext true
}
}

View file

@ -15,7 +15,7 @@ class CreateExtensionRepo(
suspend fun await(repoUrl: String): Result {
// Do not allow invalid formats & avoid adding duplicating official repo
if (!repoUrl.matches(repoRegex) || repoUrl.startsWith(OFFICIAL_REPO_BASE_URL)) {
if (!repoUrl.matches(repoRegex)) {
return Result.InvalidUrl
}
@ -70,6 +70,7 @@ class CreateExtensionRepo(
}
companion object {
const val OFFICIAL_REPO_WEBSITE = "https://komikku-app.github.io"
const val OFFICIAL_REPO_BASE_URL = "https://raw.githubusercontent.com/komikku-app/extensions/repo"
// cuong-tran's key

View file

@ -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_more">More extensions...</string>
<!-- Extension repos -->
<!-- Downloads section -->

View file

@ -40,11 +40,13 @@ fun EmptyScreen(
stringRes: StringResource,
modifier: Modifier = Modifier,
actions: ImmutableList<EmptyScreenAction>? = null,
help: @Composable (() -> Unit)? = null,
) {
EmptyScreen(
message = stringResource(stringRes),
modifier = modifier,
actions = actions,
help = help,
)
}
@ -53,6 +55,7 @@ fun EmptyScreen(
message: String,
modifier: Modifier = Modifier,
actions: ImmutableList<EmptyScreenAction>? = null,
help: @Composable (() -> Unit)? = null,
) {
val face = remember { getRandomErrorFace() }
Column(
@ -80,6 +83,8 @@ fun EmptyScreen(
textAlign = TextAlign.Center,
)
help?.let { help() }
if (!actions.isNullOrEmpty()) {
Row(
modifier = Modifier