mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-09 02:28:55 +01:00
Clean up external repos
- Accept full URL as input instead, which allows for non-GitHub - Remove automatic CDN fallback in favor of adding that as an external repo if needed
This commit is contained in:
@@ -25,7 +25,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import eu.kanade.core.preference.asToggleableState
|
||||
import eu.kanade.presentation.category.visualName
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -43,9 +42,6 @@ fun CategoryCreateDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onCreate: (String) -> Unit,
|
||||
categories: ImmutableList<String>,
|
||||
title: String,
|
||||
extraMessage: String? = null,
|
||||
alreadyExistsError: StringResource = MR.strings.error_category_exists,
|
||||
) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
|
||||
@@ -71,32 +67,28 @@ fun CategoryCreateDialog(
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = title)
|
||||
Text(text = stringResource(MR.strings.action_add_category))
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
extraMessage?.let { Text(it) }
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.focusRequester(focusRequester),
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = {
|
||||
Text(text = stringResource(MR.strings.name))
|
||||
},
|
||||
supportingText = {
|
||||
val msgRes = if (name.isNotEmpty() && nameAlreadyExists) {
|
||||
alreadyExistsError
|
||||
} else {
|
||||
MR.strings.information_required_plain
|
||||
}
|
||||
Text(text = stringResource(msgRes))
|
||||
},
|
||||
isError = name.isNotEmpty() && nameAlreadyExists,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.focusRequester(focusRequester),
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = {
|
||||
Text(text = stringResource(MR.strings.name))
|
||||
},
|
||||
supportingText = {
|
||||
val msgRes = if (name.isNotEmpty() && nameAlreadyExists) {
|
||||
MR.strings.error_category_exists
|
||||
} else {
|
||||
MR.strings.information_required_plain
|
||||
}
|
||||
Text(text = stringResource(msgRes))
|
||||
},
|
||||
isError = name.isNotEmpty() && nameAlreadyExists,
|
||||
singleLine = true,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -113,7 +105,6 @@ fun CategoryRenameDialog(
|
||||
onRename: (String) -> Unit,
|
||||
categories: ImmutableList<String>,
|
||||
category: String,
|
||||
alreadyExistsError: StringResource = MR.strings.error_category_exists,
|
||||
) {
|
||||
var name by remember { mutableStateOf(category) }
|
||||
var valueHasChanged by remember { mutableStateOf(false) }
|
||||
@@ -153,7 +144,7 @@ fun CategoryRenameDialog(
|
||||
label = { Text(text = stringResource(MR.strings.name)) },
|
||||
supportingText = {
|
||||
val msgRes = if (valueHasChanged && nameAlreadyExists) {
|
||||
alreadyExistsError
|
||||
MR.strings.error_category_exists
|
||||
} else {
|
||||
MR.strings.information_required_plain
|
||||
}
|
||||
@@ -176,8 +167,7 @@ fun CategoryRenameDialog(
|
||||
fun CategoryDeleteDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
title: String,
|
||||
text: String,
|
||||
category: String,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
@@ -195,10 +185,10 @@ fun CategoryDeleteDialog(
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = title)
|
||||
Text(text = stringResource(MR.strings.delete_category))
|
||||
},
|
||||
text = {
|
||||
Text(text = text)
|
||||
Text(text = stringResource(MR.strings.delete_category_confirmation, category))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ fun CategoryListItem(
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = "")
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = null)
|
||||
Text(
|
||||
text = category.name,
|
||||
modifier = Modifier
|
||||
@@ -61,13 +61,13 @@ fun CategoryListItem(
|
||||
onClick = { onMoveUp(category) },
|
||||
enabled = canMoveUp,
|
||||
) {
|
||||
Icon(imageVector = Icons.Outlined.ArrowDropUp, contentDescription = "")
|
||||
Icon(imageVector = Icons.Outlined.ArrowDropUp, contentDescription = null)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { onMoveDown(category) },
|
||||
enabled = canMoveDown,
|
||||
) {
|
||||
Icon(imageVector = Icons.Outlined.ArrowDropDown, contentDescription = "")
|
||||
Icon(imageVector = Icons.Outlined.ArrowDropDown, contentDescription = null)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
IconButton(onClick = onRename) {
|
||||
|
||||
@@ -9,8 +9,8 @@ import androidx.fragment.app.FragmentActivity
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.presentation.category.repos.RepoScreen
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.core.i18n.stringResource
|
||||
@@ -47,7 +47,7 @@ object SettingsBrowseScreen : SearchableSettings {
|
||||
title = stringResource(MR.strings.label_extension_repos),
|
||||
subtitle = pluralStringResource(MR.plurals.num_repos, reposCount.size, reposCount.size),
|
||||
onClick = {
|
||||
navigator.push(RepoScreen())
|
||||
navigator.push(ExtensionReposScreen())
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package eu.kanade.presentation.category.repos
|
||||
package eu.kanade.presentation.more.settings.screen.browse
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -8,23 +8,21 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||
import eu.kanade.presentation.category.SourceRepoScreen
|
||||
import eu.kanade.presentation.category.components.CategoryCreateDialog
|
||||
import eu.kanade.presentation.category.components.CategoryDeleteDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoCreateDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoDeleteDialog
|
||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionReposScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
|
||||
class RepoScreen : Screen() {
|
||||
class ExtensionReposScreen : Screen() {
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val context = LocalContext.current
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val screenModel = rememberScreenModel { RepoScreenModel() }
|
||||
val screenModel = rememberScreenModel { ExtensionReposScreenModel() }
|
||||
|
||||
val state by screenModel.state.collectAsState()
|
||||
|
||||
@@ -35,7 +33,7 @@ class RepoScreen : Screen() {
|
||||
|
||||
val successState = state as RepoScreenState.Success
|
||||
|
||||
SourceRepoScreen(
|
||||
ExtensionReposScreen(
|
||||
state = successState,
|
||||
onClickCreate = { screenModel.showDialog(RepoDialog.Create) },
|
||||
onClickDelete = { screenModel.showDialog(RepoDialog.Delete(it)) },
|
||||
@@ -45,21 +43,17 @@ class RepoScreen : Screen() {
|
||||
when (val dialog = successState.dialog) {
|
||||
null -> {}
|
||||
RepoDialog.Create -> {
|
||||
CategoryCreateDialog(
|
||||
ExtensionRepoCreateDialog(
|
||||
onDismissRequest = screenModel::dismissDialog,
|
||||
onCreate = { screenModel.createRepo(it) },
|
||||
categories = successState.repos,
|
||||
title = stringResource(MR.strings.action_add_repo),
|
||||
extraMessage = stringResource(MR.strings.action_add_repo_message),
|
||||
alreadyExistsError = MR.strings.error_repo_exists,
|
||||
)
|
||||
}
|
||||
is RepoDialog.Delete -> {
|
||||
CategoryDeleteDialog(
|
||||
ExtensionRepoDeleteDialog(
|
||||
onDismissRequest = screenModel::dismissDialog,
|
||||
onDelete = { screenModel.deleteRepos(listOf(dialog.repo)) },
|
||||
title = stringResource(MR.strings.action_delete_repo),
|
||||
text = stringResource(MR.strings.delete_repo_confirmation, dialog.repo),
|
||||
onDelete = { screenModel.deleteRepo(dialog.repo) },
|
||||
repo = dialog.repo,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package eu.kanade.presentation.category.repos
|
||||
package eu.kanade.presentation.more.settings.screen.browse
|
||||
|
||||
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.interactor.CreateSourceRepo
|
||||
import eu.kanade.domain.source.interactor.DeleteSourceRepos
|
||||
import eu.kanade.domain.source.interactor.DeleteSourceRepo
|
||||
import eu.kanade.domain.source.interactor.GetSourceRepos
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -18,10 +18,10 @@ import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class RepoScreenModel(
|
||||
class ExtensionReposScreenModel(
|
||||
private val getSourceRepos: GetSourceRepos = Injekt.get(),
|
||||
private val createSourceRepo: CreateSourceRepo = Injekt.get(),
|
||||
private val deleteSourceRepos: DeleteSourceRepos = Injekt.get(),
|
||||
private val deleteSourceRepo: DeleteSourceRepo = Injekt.get(),
|
||||
) : StateScreenModel<RepoScreenState>(RepoScreenState.Loading) {
|
||||
|
||||
private val _events: Channel<RepoEvent> = Channel(Int.MAX_VALUE)
|
||||
@@ -48,20 +48,20 @@ class RepoScreenModel(
|
||||
fun createRepo(name: String) {
|
||||
screenModelScope.launchIO {
|
||||
when (createSourceRepo.await(name)) {
|
||||
is CreateSourceRepo.Result.InvalidName -> _events.send(RepoEvent.InvalidName)
|
||||
is CreateSourceRepo.Result.InvalidUrl -> _events.send(RepoEvent.InvalidUrl)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given repos from the database.
|
||||
* Deletes the given repo from the database.
|
||||
*
|
||||
* @param repos The list of repos to delete.
|
||||
* @param repo The repo to delete.
|
||||
*/
|
||||
fun deleteRepos(repos: List<String>) {
|
||||
fun deleteRepo(repo: String) {
|
||||
screenModelScope.launchIO {
|
||||
deleteSourceRepos.await(repos)
|
||||
deleteSourceRepo.await(repo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +86,7 @@ class RepoScreenModel(
|
||||
|
||||
sealed class RepoEvent {
|
||||
sealed class LocalizedMessage(val stringRes: StringResource) : RepoEvent()
|
||||
data object InvalidName : LocalizedMessage(MR.strings.invalid_repo_name)
|
||||
data object InternalError : LocalizedMessage(MR.strings.internal_error)
|
||||
data object InvalidUrl : LocalizedMessage(MR.strings.invalid_repo_name)
|
||||
}
|
||||
|
||||
sealed class RepoDialog {
|
||||
@@ -1,9 +1,8 @@
|
||||
package eu.kanade.presentation.category.components.repo
|
||||
package eu.kanade.presentation.more.settings.screen.browse.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -24,7 +23,7 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
@Composable
|
||||
fun SourceRepoContent(
|
||||
fun ExtensionReposContent(
|
||||
repos: ImmutableList<String>,
|
||||
lazyListState: LazyListState,
|
||||
paddingValues: PaddingValues,
|
||||
@@ -38,7 +37,7 @@ fun SourceRepoContent(
|
||||
modifier = modifier,
|
||||
) {
|
||||
items(repos) { repo ->
|
||||
SourceRepoListItem(
|
||||
ExtensionRepoListItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
repo = repo,
|
||||
onDelete = { onClickDelete(repo) },
|
||||
@@ -48,7 +47,7 @@ fun SourceRepoContent(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SourceRepoListItem(
|
||||
private fun ExtensionRepoListItem(
|
||||
repo: String,
|
||||
onDelete: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -66,13 +65,16 @@ private fun SourceRepoListItem(
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = "")
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = null)
|
||||
Text(text = repo, modifier = Modifier.padding(start = MaterialTheme.padding.medium))
|
||||
}
|
||||
Row {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
IconButton(onClick = onDelete) {
|
||||
Icon(imageVector = Icons.Outlined.Delete, contentDescription = "")
|
||||
Icon(imageVector = Icons.Outlined.Delete, contentDescription = null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package eu.kanade.presentation.more.settings.screen.browse.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Composable
|
||||
fun ExtensionRepoCreateDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onCreate: (String) -> Unit,
|
||||
categories: ImmutableList<String>,
|
||||
) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val nameAlreadyExists = remember(name) { categories.contains(name) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
enabled = name.isNotEmpty() && !nameAlreadyExists,
|
||||
onClick = {
|
||||
onCreate(name)
|
||||
onDismissRequest()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(MR.strings.action_add))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(MR.strings.action_add_repo))
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
Text(text = stringResource(MR.strings.action_add_repo_message))
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.focusRequester(focusRequester),
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = {
|
||||
Text(text = stringResource(MR.strings.label_add_repo_input))
|
||||
},
|
||||
supportingText = {
|
||||
val msgRes = if (name.isNotEmpty() && nameAlreadyExists) {
|
||||
MR.strings.error_repo_exists
|
||||
} else {
|
||||
MR.strings.information_required_plain
|
||||
}
|
||||
Text(text = stringResource(msgRes))
|
||||
},
|
||||
isError = name.isNotEmpty() && nameAlreadyExists,
|
||||
singleLine = true,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
LaunchedEffect(focusRequester) {
|
||||
// TODO: https://issuetracker.google.com/issues/204502668
|
||||
delay(0.1.seconds)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExtensionRepoDeleteDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
repo: String,
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onDelete()
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text(text = stringResource(MR.strings.action_ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(text = stringResource(MR.strings.action_cancel))
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(MR.strings.action_delete_repo))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(MR.strings.delete_repo_confirmation, repo))
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
package eu.kanade.presentation.category
|
||||
@file:JvmName("ExtensionReposScreenKt")
|
||||
|
||||
package eu.kanade.presentation.more.settings.screen.browse.components
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -7,9 +9,8 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
||||
import eu.kanade.presentation.category.components.repo.SourceRepoContent
|
||||
import eu.kanade.presentation.category.repos.RepoScreenState
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.more.settings.screen.browse.RepoScreenState
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
@@ -19,7 +20,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
import tachiyomi.presentation.core.util.plus
|
||||
|
||||
@Composable
|
||||
fun SourceRepoScreen(
|
||||
fun ExtensionReposScreen(
|
||||
state: RepoScreenState.Success,
|
||||
onClickCreate: () -> Unit,
|
||||
onClickDelete: (String) -> Unit,
|
||||
@@ -49,7 +50,7 @@ fun SourceRepoScreen(
|
||||
return@Scaffold
|
||||
}
|
||||
|
||||
SourceRepoContent(
|
||||
ExtensionReposContent(
|
||||
repos = state.repos,
|
||||
lazyListState = lazyListState,
|
||||
paddingValues = paddingValues + topSmallPaddingValues +
|
||||
Reference in New Issue
Block a user