mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-21 20:47:03 -05:00
Enable confirmButton
only when needed to respond to user input (#8848)
* Enable `confirmButton` when appropriate * Show error in dialog instead * Follow M3 guidelines
This commit is contained in:
parent
62480f090b
commit
33a2219716
12 changed files with 68 additions and 47 deletions
|
@ -1,7 +1,6 @@
|
|||
package eu.kanade.domain.category.interactor
|
||||
|
||||
import eu.kanade.domain.category.model.Category
|
||||
import eu.kanade.domain.category.model.anyWithName
|
||||
import eu.kanade.domain.category.repository.CategoryRepository
|
||||
import eu.kanade.domain.library.service.LibraryPreferences
|
||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
||||
|
@ -23,10 +22,6 @@ class CreateCategoryWithName(
|
|||
|
||||
suspend fun await(name: String): Result = withNonCancellableContext {
|
||||
val categories = categoryRepository.getAll()
|
||||
if (categories.anyWithName(name)) {
|
||||
return@withNonCancellableContext Result.NameAlreadyExistsError
|
||||
}
|
||||
|
||||
val nextOrder = categories.maxOfOrNull { it.order }?.plus(1) ?: 0
|
||||
val newCategory = Category(
|
||||
id = 0,
|
||||
|
@ -46,7 +41,6 @@ class CreateCategoryWithName(
|
|||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object NameAlreadyExistsError : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package eu.kanade.domain.category.interactor
|
|||
|
||||
import eu.kanade.domain.category.model.Category
|
||||
import eu.kanade.domain.category.model.CategoryUpdate
|
||||
import eu.kanade.domain.category.model.anyWithName
|
||||
import eu.kanade.domain.category.repository.CategoryRepository
|
||||
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
|
@ -13,11 +12,6 @@ class RenameCategory(
|
|||
) {
|
||||
|
||||
suspend fun await(categoryId: Long, name: String) = withNonCancellableContext {
|
||||
val categories = categoryRepository.getAll()
|
||||
if (categories.anyWithName(name)) {
|
||||
return@withNonCancellableContext Result.NameAlreadyExistsError
|
||||
}
|
||||
|
||||
val update = CategoryUpdate(
|
||||
id = categoryId,
|
||||
name = name,
|
||||
|
@ -36,7 +30,6 @@ class RenameCategory(
|
|||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object NameAlreadyExistsError : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import androidx.compose.ui.focus.FocusRequester
|
|||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.domain.category.model.Category
|
||||
import eu.kanade.domain.category.model.anyWithName
|
||||
import eu.kanade.tachiyomi.R
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
@ -23,17 +24,23 @@ import kotlin.time.Duration.Companion.seconds
|
|||
fun CategoryCreateDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onCreate: (String) -> Unit,
|
||||
categories: List<Category>,
|
||||
) {
|
||||
var name by remember { mutableStateOf("") }
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val nameAlreadyExists = remember(name) { categories.anyWithName(name) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onCreate(name)
|
||||
onDismissRequest()
|
||||
},) {
|
||||
TextButton(
|
||||
enabled = name.isNotEmpty() && !nameAlreadyExists,
|
||||
onClick = {
|
||||
onCreate(name)
|
||||
onDismissRequest()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(R.string.action_add))
|
||||
}
|
||||
},
|
||||
|
@ -47,13 +54,15 @@ fun CategoryCreateDialog(
|
|||
},
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.focusRequester(focusRequester),
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = {
|
||||
Text(text = stringResource(R.string.name))
|
||||
label = { Text(text = stringResource(R.string.name)) },
|
||||
supportingText = {
|
||||
val msgRes = if (name.isNotEmpty() && nameAlreadyExists) R.string.error_category_exists else R.string.information_required_plain
|
||||
Text(text = stringResource(msgRes))
|
||||
},
|
||||
isError = name.isNotEmpty() && nameAlreadyExists,
|
||||
singleLine = true,
|
||||
)
|
||||
},
|
||||
|
@ -70,18 +79,25 @@ fun CategoryCreateDialog(
|
|||
fun CategoryRenameDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onRename: (String) -> Unit,
|
||||
categories: List<Category>,
|
||||
category: Category,
|
||||
) {
|
||||
var name by remember { mutableStateOf(category.name) }
|
||||
var valueHasChanged by remember { mutableStateOf(false) }
|
||||
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val nameAlreadyExists = remember(name) { categories.anyWithName(name) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onRename(name)
|
||||
onDismissRequest()
|
||||
},) {
|
||||
TextButton(
|
||||
enabled = valueHasChanged && !nameAlreadyExists,
|
||||
onClick = {
|
||||
onRename(name)
|
||||
onDismissRequest()
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
|
@ -95,13 +111,18 @@ fun CategoryRenameDialog(
|
|||
},
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier
|
||||
.focusRequester(focusRequester),
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = {
|
||||
Text(text = stringResource(R.string.name))
|
||||
onValueChange = {
|
||||
valueHasChanged = name != it
|
||||
name = it
|
||||
},
|
||||
label = { Text(text = stringResource(R.string.name)) },
|
||||
supportingText = {
|
||||
val msgRes = if (valueHasChanged && nameAlreadyExists) R.string.error_category_exists else R.string.information_required_plain
|
||||
Text(text = stringResource(msgRes))
|
||||
},
|
||||
isError = valueHasChanged && nameAlreadyExists,
|
||||
singleLine = true,
|
||||
)
|
||||
},
|
||||
|
|
|
@ -44,6 +44,7 @@ fun DeleteLibraryMangaDialog(
|
|||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
enabled = list.any { it.isChecked },
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onConfirm(
|
||||
|
|
|
@ -42,6 +42,7 @@ fun DownloadCustomAmountDialog(
|
|||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
enabled = amount != 0,
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onConfirm(amount.coerceIn(0, maxAmount))
|
||||
|
|
|
@ -275,10 +275,6 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||
pref = userAgentPref,
|
||||
title = stringResource(R.string.pref_user_agent_string),
|
||||
onValueChanged = {
|
||||
if (it.isBlank()) {
|
||||
context.toast(R.string.error_user_agent_string_blank)
|
||||
return@EditTextPreference false
|
||||
}
|
||||
try {
|
||||
// OkHttp checks for valid values internally
|
||||
Headers.Builder().add("User-Agent", it)
|
||||
|
|
|
@ -315,7 +315,10 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { onValueChanged(portraitValue, landscapeValue) }) {
|
||||
TextButton(
|
||||
enabled = portraitValue != initialPortrait || landscapeValue != initialLandscape,
|
||||
onClick = { onValueChanged(portraitValue, landscapeValue) },
|
||||
) {
|
||||
Text(text = stringResource(android.R.string.ok))
|
||||
}
|
||||
},
|
||||
|
|
|
@ -222,7 +222,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||
label = { Text(text = stringResource(uNameStringRes)) },
|
||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
|
||||
singleLine = true,
|
||||
isError = inputError && username.text.isEmpty(),
|
||||
isError = inputError && !processing,
|
||||
)
|
||||
|
||||
var hidePassword by remember { mutableStateOf(true) }
|
||||
|
@ -253,21 +253,16 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||
imeAction = ImeAction.Done,
|
||||
),
|
||||
singleLine = true,
|
||||
isError = inputError && password.text.isEmpty(),
|
||||
isError = inputError && !processing,
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = !processing,
|
||||
enabled = !processing && username.text.isNotBlank() && password.text.isNotBlank(),
|
||||
onClick = {
|
||||
if (username.text.isEmpty() || password.text.isEmpty()) {
|
||||
inputError = true
|
||||
return@Button
|
||||
}
|
||||
scope.launchIO {
|
||||
inputError = false
|
||||
processing = true
|
||||
val result = checkLogin(
|
||||
context = context,
|
||||
|
@ -275,6 +270,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||
username = username.text,
|
||||
password = password.text,
|
||||
)
|
||||
inputError = !result
|
||||
if (result) onDismissRequest()
|
||||
processing = false
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
package eu.kanade.presentation.more.settings.widget
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Cancel
|
||||
import androidx.compose.material.icons.filled.Error
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
|
@ -50,6 +55,16 @@ fun EditTextPreferenceWidget(
|
|||
OutlinedTextField(
|
||||
value = textFieldValue,
|
||||
onValueChange = { textFieldValue = it },
|
||||
trailingIcon = {
|
||||
if (textFieldValue.text.isBlank()) {
|
||||
Icon(imageVector = Icons.Filled.Error, contentDescription = null)
|
||||
} else {
|
||||
IconButton(onClick = { textFieldValue = TextFieldValue("") }) {
|
||||
Icon(imageVector = Icons.Filled.Cancel, contentDescription = null)
|
||||
}
|
||||
}
|
||||
},
|
||||
isError = textFieldValue.text.isBlank(),
|
||||
singleLine = true,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
@ -59,6 +74,7 @@ fun EditTextPreferenceWidget(
|
|||
),
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
enabled = textFieldValue.text != value && textFieldValue.text.isNotBlank(),
|
||||
onClick = {
|
||||
scope.launch {
|
||||
if (onConfirm(textFieldValue.text)) {
|
||||
|
|
|
@ -52,13 +52,15 @@ class CategoryScreen : Screen {
|
|||
CategoryDialog.Create -> {
|
||||
CategoryCreateDialog(
|
||||
onDismissRequest = screenModel::dismissDialog,
|
||||
onCreate = { screenModel.createCategory(it) },
|
||||
onCreate = screenModel::createCategory,
|
||||
categories = successState.categories,
|
||||
)
|
||||
}
|
||||
is CategoryDialog.Rename -> {
|
||||
CategoryRenameDialog(
|
||||
onDismissRequest = screenModel::dismissDialog,
|
||||
onRename = { screenModel.renameCategory(dialog.category, it) },
|
||||
categories = successState.categories,
|
||||
category = dialog.category,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -47,7 +47,6 @@ class CategoryScreenModel(
|
|||
coroutineScope.launch {
|
||||
when (createCategoryWithName.await(name)) {
|
||||
is CreateCategoryWithName.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
||||
CreateCategoryWithName.Result.NameAlreadyExistsError -> _events.send(CategoryEvent.CategoryWithNameAlreadyExists)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +83,6 @@ class CategoryScreenModel(
|
|||
coroutineScope.launch {
|
||||
when (renameCategory.await(category, name)) {
|
||||
is RenameCategory.Result.InternalError -> _events.send(CategoryEvent.InternalError)
|
||||
RenameCategory.Result.NameAlreadyExistsError -> _events.send(CategoryEvent.CategoryWithNameAlreadyExists)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +115,6 @@ sealed class CategoryDialog {
|
|||
|
||||
sealed class CategoryEvent {
|
||||
sealed class LocalizedMessage(@StringRes val stringRes: Int) : CategoryEvent()
|
||||
object CategoryWithNameAlreadyExists : LocalizedMessage(R.string.error_category_exists)
|
||||
object InternalError : LocalizedMessage(R.string.internal_error)
|
||||
}
|
||||
|
||||
|
|
|
@ -882,6 +882,7 @@
|
|||
<string name="information_empty_category">You have no categories. Tap the plus button to create one for organizing your library.</string>
|
||||
<string name="information_empty_category_dialog">You don\'t have any categories yet.</string>
|
||||
<string name="information_cloudflare_bypass_failure">Failed to bypass Cloudflare</string>
|
||||
<string name="information_required_plain">*required</string>
|
||||
<!-- Do not translate "WebView" -->
|
||||
<string name="information_webview_required">WebView is required for Tachiyomi</string>
|
||||
<!-- Do not translate "WebView" -->
|
||||
|
|
Loading…
Reference in a new issue