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) 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 extensionRepos() = preferenceStore.getStringSet("extension_repos", emptySet())
fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0) fun extensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)

View file

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

View file

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

View file

@ -3,18 +3,28 @@
package eu.kanade.presentation.more.settings.screen.browse.components package eu.kanade.presentation.more.settings.screen.browse.components
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Help
import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier 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.category.components.CategoryFloatingActionButton
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.more.settings.screen.browse.RepoScreenState 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 mihon.domain.extensionrepo.model.ExtensionRepo
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
@ -58,9 +68,20 @@ fun ExtensionReposScreen(
}, },
) { paddingValues -> ) { paddingValues ->
if (state.isEmpty) { if (state.isEmpty) {
val context = LocalContext.current
EmptyScreen( EmptyScreen(
MR.strings.information_empty_repos, MR.strings.information_empty_repos,
modifier = Modifier.padding(paddingValues), 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 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.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import logcat.LogPriority 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.GetExtensionRepo
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
import mihon.domain.extensionrepo.model.ExtensionRepo import mihon.domain.extensionrepo.model.ExtensionRepo
@ -48,22 +46,11 @@ internal class ExtensionApi {
} }
suspend fun findExtensions(): List<Extension.Available> { 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 { return withIOContext {
buildList { getExtensionRepo.getAll()
// Combine built-in OFFICIAL repo's extensions list with repo's list .map { async { getExtensions(it) } }
addAll(getExtensions(officialRepo)) .awaitAll()
getExtensionRepo.getAll() .flatten()
.map { async { getExtensions(it) } }
.awaitAll()
.flatten()
}
} }
} }

View file

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

View file

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

View file

@ -45,4 +45,7 @@ val migrations: List<Migration>
MoveCacheToDiskSettingMigration(), MoveCacheToDiskSettingMigration(),
MoveEncryptionSettingsToAppStateMigration(), MoveEncryptionSettingsToAppStateMigration(),
TrustExtensionRepositoryMigration(), 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 { suspend fun await(repoUrl: String): Result {
// Do not allow invalid formats & avoid adding duplicating official repo // 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 return Result.InvalidUrl
} }
@ -70,6 +70,7 @@ class CreateExtensionRepo(
} }
companion object { 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" const val OFFICIAL_REPO_BASE_URL = "https://raw.githubusercontent.com/komikku-app/extensions/repo"
// cuong-tran's key // cuong-tran's key

View file

@ -53,6 +53,7 @@
<!-- Extension section --> <!-- Extension section -->
<string name="ext_unofficial">Unofficial</string> <string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</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 --> <!-- Extension repos -->
<!-- Downloads section --> <!-- Downloads section -->

View file

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