rename AppTextField to AppOutlinedTextField and implement InspiringSearchField

This commit is contained in:
jonasgaudian
2026-02-13 15:15:16 +01:00
parent e5c58f58f6
commit b3e73db956
26 changed files with 344 additions and 203 deletions

View File

@@ -0,0 +1,231 @@
package eu.gaudian.translator.view.composable
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
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.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
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.graphics.Color
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R
import kotlinx.coroutines.delay
/**
* Helper composable for consistent OutlinedTextField colors.
*/
@Composable
private fun appTextFieldColors() = OutlinedTextFieldDefaults.colors(
focusedBorderColor = MaterialTheme.colorScheme.primary,
unfocusedBorderColor = MaterialTheme.colorScheme.onSurface.copy(alpha = ComponentDefaults.ALPHA_LOW),
focusedLabelColor = MaterialTheme.colorScheme.primary,
cursorColor = MaterialTheme.colorScheme.primary
)
/**
* A styled text input field.
*
* @param value The input text to be shown in the text field.
* @param onValueChange The callback that is triggered when the input service updates the text.
* @param modifier The modifier to be applied to the text field.
* @param label A composable lambda for the label to be displayed inside the text field.
* @param trailingIcon A composable lambda for the trailing icon.
* @param paste Controls the visibility of the paste icon.
* @param clear Controls the visibility of the clear icon.
*/
@Composable
fun AppOutlinedTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
label: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
singleLine: Boolean = false,
minLines: Int = 1,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
placeholder: @Composable (() -> Unit)? = null,
enabled: Boolean = true,
readOnly: Boolean = false,
leadingIcon: @Composable (() -> Unit)? = null,
paste: Boolean = false,
clear: Boolean = false,
supportingText: @Composable (() -> Unit)? = null,
) {
val clipboardManager = LocalClipboardManager.current
val finalTrailingIcon: @Composable (() -> Unit)? = if (paste || clear || trailingIcon != null) {
{
Row(verticalAlignment = Alignment.CenterVertically) {
if (paste && value.isEmpty()) {
IconButton(onClick = { onValueChange(clipboardManager.getText()?.text ?: "") }) {
Icon(
imageVector = AppIcons.Paste,
contentDescription = stringResource(R.string.cd_paste),
tint = MaterialTheme.colorScheme.primary
)
}
}
if (clear && value.isNotEmpty()) {
IconButton(onClick = { onValueChange("") }) {
Icon(
imageVector = AppIcons.Clear,
contentDescription = stringResource(R.string.label_clear),
tint = MaterialTheme.colorScheme.primary
)
}
}
if (trailingIcon != null) {
trailingIcon()
}
}
}
} else {
null
}
OutlinedTextField(
value = value,
onValueChange = onValueChange,
modifier = modifier.fillMaxWidth(),
label = label,
trailingIcon = finalTrailingIcon,
shape = ComponentDefaults.DefaultShape,
colors = appTextFieldColors(),
placeholder = placeholder,
enabled = enabled,
readOnly = readOnly,
leadingIcon = leadingIcon,
minLines = minLines,
maxLines = maxLines,
supportingText = supportingText,
)
}
@Composable
fun InspiringSearchField(
value: String,
hints : Array<String>,
onValueChange: (String) -> Unit
) {
var currentHintIndex by remember { mutableIntStateOf(0) }
// Start rotating immediately when the dialog appears
LaunchedEffect(hints) {
if (hints.isNotEmpty()) {
while (true) {
delay(2800)
currentHintIndex = (currentHintIndex + 1) % hints.size
}
}
}
AppOutlinedTextField(
value = value,
onValueChange = onValueChange,
//label = { Text(stringResource(R.string.text_search_term)) },
modifier = Modifier.fillMaxWidth(),
placeholder = {
AnimatedContent(
targetState = if (hints.isNotEmpty()) hints[currentHintIndex] else "",
transitionSpec = {
(fadeIn(tween(400)) + slideInVertically { it / 2 })
.togetherWith(fadeOut(tween(400)) + slideOutVertically { -it / 2 })
},
label = "HintAnimation"
) { hint ->
Text(
text = hint,
color = Color.Gray.copy(alpha = 0.6f),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
},
singleLine = true,
minLines = 1,
maxLines = 1
)
}
@Suppress("HardCodedStringLiteral")
@Preview
@Composable
fun AppOutlinedTextFieldPreview() {
var text by remember { mutableStateOf("Test") }
AppOutlinedTextField(
value = text,
onValueChange = { },
label = { Text(stringResource(R.string.email_address)) }
)
}
@Suppress("HardCodedStringLiteral")
@Preview(showBackground = true, name = "Search Field Preview")
@Composable
fun PreviewInspiringSearchField() {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxWidth(),
color = Color(0xFFF8F9FA) // Light gray background like in your screenshot
) {
Column(
modifier = Modifier
.padding(24.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
text = "Deixe a IA encontrar\nvocabulário para você!",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold
)
// Our animated component
InspiringSearchField(
value = "Test",
hints = listOf("test1", "test2", "test3").toTypedArray(),
onValueChange = {}
)
// Placeholder for the rest of your UI
Button(
onClick = { },
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(8.dp)
) {
Text("Continuar")
}
}
}
}
}

View File

@@ -30,12 +30,9 @@ import androidx.compose.material3.Checkbox
import androidx.compose.material3.CheckboxDefaults
import androidx.compose.material3.FabPosition
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScaffoldDefaults
import androidx.compose.material3.Surface
@@ -57,7 +54,6 @@ import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -449,106 +445,9 @@ fun SecondaryButtonInversePreview() {
SecondaryButton(onClick = { }, text = stringResource(R.string.secondary_inverse), icon = AppIcons.Add, inverse = true)
}
/**
* Helper composable for consistent OutlinedTextField colors.
*/
@Composable
private fun appTextFieldColors() = OutlinedTextFieldDefaults.colors(
focusedBorderColor = MaterialTheme.colorScheme.primary,
unfocusedBorderColor = MaterialTheme.colorScheme.onSurface.copy(alpha = ComponentDefaults.ALPHA_LOW),
focusedLabelColor = MaterialTheme.colorScheme.primary,
cursorColor = MaterialTheme.colorScheme.primary
)
/**
* A styled text input field.
*
* @param value The input text to be shown in the text field.
* @param onValueChange The callback that is triggered when the input service updates the text.
* @param modifier The modifier to be applied to the text field.
* @param label A composable lambda for the label to be displayed inside the text field.
* @param trailingIcon A composable lambda for the trailing icon.
* @param paste Controls the visibility of the paste icon.
* @param clear Controls the visibility of the clear icon.
*/
@Composable
fun AppTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
label: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
singleLine: Boolean = false,
minLines: Int = 1,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
placeholder: @Composable (() -> Unit)? = null,
enabled: Boolean = true,
readOnly: Boolean = false,
leadingIcon: @Composable (() -> Unit)? = null,
paste: Boolean = false,
clear: Boolean = false,
supportingText: @Composable (() -> Unit)? = null,
) {
val clipboardManager = LocalClipboardManager.current
val finalTrailingIcon: @Composable (() -> Unit)? = if (paste || clear || trailingIcon != null) {
{
Row(verticalAlignment = Alignment.CenterVertically) {
if (paste && value.isEmpty()) {
IconButton(onClick = { onValueChange(clipboardManager.getText()?.text ?: "") }) {
Icon(
imageVector = AppIcons.Paste,
contentDescription = stringResource(R.string.cd_paste),
tint = MaterialTheme.colorScheme.primary
)
}
}
if (clear && value.isNotEmpty()) {
IconButton(onClick = { onValueChange("") }) {
Icon(
imageVector = AppIcons.Clear,
contentDescription = stringResource(R.string.label_clear),
tint = MaterialTheme.colorScheme.primary
)
}
}
if (trailingIcon != null) {
trailingIcon()
}
}
}
} else {
null
}
OutlinedTextField(
value = value,
onValueChange = onValueChange,
modifier = modifier.fillMaxWidth(),
label = label,
trailingIcon = finalTrailingIcon,
shape = ComponentDefaults.DefaultShape,
colors = appTextFieldColors(),
placeholder = placeholder,
enabled = enabled,
readOnly = readOnly,
leadingIcon = leadingIcon,
minLines = minLines,
maxLines = maxLines,
supportingText = supportingText,
)
}
@Preview
@Composable
fun AppTextFieldPreview() {
var text by remember { mutableStateOf("") }
AppTextField(
value = text,
onValueChange = { text = it },
label = { Text(stringResource(R.string.email_address)) }
)
}
@Composable

View File

@@ -46,7 +46,7 @@ import eu.gaudian.translator.model.VocabularyStage
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.MultipleLanguageDropdown
import eu.gaudian.translator.view.hints.CategoryHint
import eu.gaudian.translator.viewmodel.CategoryViewModel
@@ -96,7 +96,7 @@ fun AddCategoryDialog(
Spacer(modifier = Modifier.height(16.dp))
AppTextField(
AppOutlinedTextField(
value = categoryName,
onValueChange = { categoryName = it },
label = { Text(stringResource(R.string.text_category_name)) },

View File

@@ -22,7 +22,7 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.DialogButton
@Composable
@@ -56,33 +56,37 @@ fun AddCustomLanguageDialog(
modifier = Modifier.padding(bottom = 8.dp)
)
AppTextField(
AppOutlinedTextField(
value = languageCode,
onValueChange = { languageCode = it },
label = { Text(text = stringResource(R.string.text_language_code)) },
placeholder = { Text(text = stringResource(R.string.text_e_g_en)) },
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
AppTextField(
AppOutlinedTextField(
value = languageRegion,
onValueChange = { languageRegion = it },
label = { Text(text = stringResource(R.string.text_region)) },
placeholder = { Text(text = stringResource(R.string.text_e_g_us)) },
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
AppTextField(
AppOutlinedTextField(
value = languageName,
onValueChange = { languageName = it },
label = { Text(text = stringResource(R.string.text_name_of_the_language)) },
placeholder = { Text(text = stringResource(R.string.text_e_g_english)) },
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
AppTextField(
AppOutlinedTextField(
value = englishName,
onValueChange = { englishName = it },
label = { Text(text = stringResource(R.string.name_in_english)) },
placeholder = { Text(text = stringResource(R.string.text_e_g_english)) },
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
singleLine = true
)
Row(

View File

@@ -46,7 +46,7 @@ import eu.gaudian.translator.view.LocalConnectionConfigured
import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppCheckbox
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.SourceLanguageDropdown
import eu.gaudian.translator.view.composable.TargetLanguageDropdown
import eu.gaudian.translator.viewmodel.LanguageViewModel
@@ -136,7 +136,7 @@ fun AddVocabularyDialog(
}
if (selectedTab != VocabularyDialogTab.TEXT) {
AppTextField(
AppOutlinedTextField(
value = word,
onValueChange = { word = it },
label = { Text(stringResource(R.string.text_label_word)) },
@@ -165,7 +165,7 @@ fun AddVocabularyDialog(
modifier = Modifier.fillMaxWidth()
) { Text(stringResource(R.string.label_translate)) }
}
AppTextField(
AppOutlinedTextField(
value = singleTranslation,
onValueChange = { singleTranslation = it },
label = { Text(stringResource(R.string.text_translation)) },
@@ -238,7 +238,7 @@ fun AddVocabularyDialog(
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(stringResource(R.string.text_enter_a_text_to_extract))
Box(Modifier.fillMaxWidth().padding(0.dp).heightIn(max = 300.dp)){
AppTextField(
AppOutlinedTextField(
value = word,
onValueChange = { word = it },
label = { Text(stringResource(R.string.label_enter_a_text)) },

View File

@@ -35,7 +35,7 @@ import eu.gaudian.translator.view.composable.AppCheckbox
import eu.gaudian.translator.view.composable.AppDropdownMenuItem
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedButton
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.viewmodel.CategoryViewModel
@@ -180,7 +180,7 @@ fun CategoryDropdown(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
AppTextField(
AppOutlinedTextField(
value = newCategoryName,
onValueChange = { newCategoryName = it },
modifier = Modifier.weight(1f),

View File

@@ -22,7 +22,7 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
@Composable
fun CreateCategoryListDialog(
@@ -57,7 +57,7 @@ fun CreateCategoryListDialog(
modifier = Modifier.padding(bottom = 8.dp)
)
AppTextField(
AppOutlinedTextField(
value = categoryName,
onValueChange = { categoryName = it },
label = { Text(stringResource(R.string.text_category_name)) },

View File

@@ -26,7 +26,7 @@ import eu.gaudian.translator.model.VocabularyCategory
import eu.gaudian.translator.model.VocabularyFilter
import eu.gaudian.translator.model.VocabularyStage
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.MultipleLanguageDropdown
import eu.gaudian.translator.viewmodel.CategoryViewModel
import eu.gaudian.translator.viewmodel.LanguageViewModel
@@ -84,7 +84,7 @@ fun EditCategoryDialog(
.fillMaxWidth()
.padding(bottom = 16.dp)
) {
AppTextField(
AppOutlinedTextField(
value = categoryName,
onValueChange = { categoryName = it },
label = { Text(stringResource(R.string.text_category_name)) },

View File

@@ -22,7 +22,7 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.DialogButton
@Composable
@@ -54,7 +54,7 @@ fun EditLanguageDialog(
)
// Language name: editable only for custom languages
AppTextField(
AppOutlinedTextField(
value = name,
onValueChange = { if (language.isCustom == true) name = it },
enabled = language.isCustom == true,
@@ -62,14 +62,14 @@ fun EditLanguageDialog(
modifier = Modifier.fillMaxWidth()
)
AppTextField(
AppOutlinedTextField(
value = code,
onValueChange = { code = it },
label = { Text(text = stringResource(R.string.text_language_code)) },
placeholder = { Text(text = stringResource(R.string.text_e_g_en)) },
modifier = Modifier.fillMaxWidth()
)
AppTextField(
AppOutlinedTextField(
value = region,
onValueChange = { region = it },
label = { Text(text = stringResource(R.string.text_region)) },

View File

@@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.width
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@@ -25,6 +25,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -39,8 +40,8 @@ import eu.gaudian.translator.R
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppSlider
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.DialogButton
import eu.gaudian.translator.view.composable.InspiringSearchField
import eu.gaudian.translator.view.composable.SourceLanguageDropdown
import eu.gaudian.translator.view.composable.TargetLanguageDropdown
import eu.gaudian.translator.view.hints.getImportVocabularyHint
@@ -117,27 +118,26 @@ fun ImportDialogContent(
) {
if (isGenerating) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
modifier = Modifier.fillMaxWidth().padding(16.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
} else {
AppTextField(
value = category,
onValueChange = { category = it },
label = { Text(stringResource(R.string.text_search_term)) },
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp)
)
Text(
text = stringResource(R.string.text_hint_you_can_search),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.fillMaxWidth()
text = stringResource(R.string.text_search_term),
style = MaterialTheme.typography.labelLarge,
modifier = Modifier.padding(bottom = 8.dp)
)
// Modern rotating field using XML resource array
InspiringSearchField(
value = category,
hints = stringArrayResource(R.array.vocabulary_hints),
onValueChange = { category = it }
)
// The "Dica" string has been removed to keep the interface clean
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(R.string.text_select_languages),
@@ -172,14 +172,11 @@ fun ImportDialogContent(
Text(
text = stringResource(R.string.text_amount_2d, amount.toInt()),
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
)
Spacer(modifier = Modifier.height(8.dp))
}
// Action buttons
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
@@ -190,7 +187,7 @@ fun ImportDialogContent(
content = { Text(stringResource(R.string.label_cancel)) }
)
if (category.isNotBlank() && !isGenerating) {
Spacer(modifier = Modifier.widthIn(8.dp))
Spacer(modifier = Modifier.width(8.dp))
DialogButton(onClick = {
coroutineScope.launch {
vocabularyViewModel.generateVocabularyItems(category, amount.toInt())

View File

@@ -29,7 +29,7 @@ import eu.gaudian.translator.R
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppOutlinedCard
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.DictionaryLanguageDropDown
import eu.gaudian.translator.viewmodel.DictionaryViewModel
import eu.gaudian.translator.viewmodel.LanguageViewModel
@@ -49,7 +49,7 @@ fun EtymologyScreen(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
AppTextField(
AppOutlinedTextField(
value = searchQuery,
onValueChange = { searchQuery = it },
label = { Text(stringResource(R.string.search_for_a_word_s_origin)) },

View File

@@ -54,7 +54,7 @@ import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedButton
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.DictionaryLanguageDropDown
import eu.gaudian.translator.view.composable.OptionItemSwitch
import eu.gaudian.translator.viewmodel.DictionaryViewModel
@@ -243,11 +243,10 @@ private fun SearchFieldWithSuggestions(
expanded = expanded && suggestions.isNotEmpty(),
onExpandedChange = { expanded = !expanded }
) {
AppTextField(
AppOutlinedTextField(
value = searchQuery,
onValueChange = {
onSearchQueryChange(it)
// Using ViewModel's fetchSuggestions with debounce
lastJob?.cancel()
lastJob = scope.launch {
delay(100)

View File

@@ -42,8 +42,8 @@ import eu.gaudian.translator.R
import eu.gaudian.translator.model.Question
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.AppSlider
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.DialogButton
import eu.gaudian.translator.view.composable.SourceLanguageDropdown
import eu.gaudian.translator.view.composable.TargetLanguageDropdown
@@ -94,7 +94,7 @@ fun GenerateExerciseDialog(
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(bottom = 8.dp)
)
AppTextField(
AppOutlinedTextField(
value = category,
onValueChange = { category = it },
label = { Text(stringResource(R.string.text_category_prompt)) },

View File

@@ -49,7 +49,7 @@ import eu.gaudian.translator.utils.TextToSpeechHelper
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.viewmodel.SettingsViewModel
import kotlinx.coroutines.launch
import java.util.Locale
@@ -102,7 +102,7 @@ fun FillInTheBlankQuestionUi(
}
}
}
AppTextField(
AppOutlinedTextField(
value = selectedAnswer,
onValueChange = onAnswerSelect,
placeholder = { Text(placeholder) },
@@ -337,7 +337,7 @@ fun ListeningComprehensionQuestionUi(
if (maskedHint.isNotBlank()) {
Text(maskedHint, style = MaterialTheme.typography.bodyMedium)
}
AppTextField(
AppOutlinedTextField(
value = selectedAnswer,
onValueChange = onAnswerSelect,
label = { Text(stringResource(R.string.type_what_you_hear)) },
@@ -482,7 +482,7 @@ fun VocabularyTestQuestionUi(
text = stringResource(R.string.translate_the_following_d, question.languageDirection),
style = MaterialTheme.typography.bodyMedium
)
AppTextField(
AppOutlinedTextField(
value = selectedAnswer,
onValueChange = onAnswerSelect,
label = { Text(stringResource(R.string.label_your_translation)) },

View File

@@ -29,7 +29,7 @@ import eu.gaudian.translator.R
import eu.gaudian.translator.model.Language
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.DialogButton
import eu.gaudian.translator.view.composable.SourceLanguageDropdown
import eu.gaudian.translator.view.composable.TargetLanguageDropdown
@@ -64,12 +64,14 @@ fun YouTubeExerciseDialog(
)
if (!languageOnly) {
AppTextField(
AppOutlinedTextField(
value = youtubeUrl,
onValueChange = { youtubeUrl = it },
label = { Text(stringResource(R.string.text_youtube_link)) },
placeholder = { Text("https://www.youtube.com/watch?v=...") },
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
singleLine = true,
paste = true,
)
}

View File

@@ -16,8 +16,8 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppCheckbox
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.AppSlider
import eu.gaudian.translator.view.composable.AppTextField
/**
* Provides the migrated Hint for ImportVocabulary.
@@ -38,7 +38,7 @@ fun getImportVocabularyHint(): Hint {
description = stringResource(R.string.text_hint_you_can_search),
trailing = {
// The AppTextField is wrapped in the VisualStep's trailing composable
AppTextField(
AppOutlinedTextField(
value = stringResource(R.string.search_term_placeholder),
onValueChange = {},
label = { Text(stringResource(R.string.text_search_term)) },

View File

@@ -36,7 +36,7 @@ import androidx.compose.ui.unit.sp
import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
object SortingScreenHint : LegacyHint() {
override val titleRes: Int = R.string.sorting_hint_title
@@ -56,7 +56,7 @@ object SortingScreenHint : LegacyHint() {
HintSection(title = stringResource(R.string.sorting_hint_intro_text)) {
@Suppress("HardCodedStringLiteral")
AppTextField(
AppOutlinedTextField(
value = "der Hund",
onValueChange = {},
label = { Text(stringResource(R.string.label_word)) },
@@ -64,7 +64,7 @@ object SortingScreenHint : LegacyHint() {
enabled = false
)
@Suppress("HardCodedStringLiteral")
AppTextField(
AppOutlinedTextField(
value = "the dog",
onValueChange = {},
label = { Text(stringResource(R.string.label_translation)) },

View File

@@ -53,9 +53,9 @@ import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppSwitch
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppTopAppBar
import eu.gaudian.translator.view.composable.ModelBadges
import eu.gaudian.translator.view.hints.AddModelScanHint
@@ -272,7 +272,7 @@ fun AddModelScreen(navController: NavController, providerKey: String) {
}
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
AppTextField(
AppOutlinedTextField(
value = displayName,
onValueChange = { displayName = it },
label = { Text(stringResource(R.string.label_display_name)) },
@@ -289,7 +289,7 @@ fun AddModelScreen(navController: NavController, providerKey: String) {
}
}
)
AppTextField(
AppOutlinedTextField(
value = modelId,
onValueChange = { modelId = it },
label = { Text(stringResource(R.string.label_model_id_star)) },
@@ -312,7 +312,7 @@ fun AddModelScreen(navController: NavController, providerKey: String) {
}
}
)
AppTextField(
AppOutlinedTextField(
value = description,
onValueChange = { description = it },
label = { Text(stringResource(R.string.label_description)) },
@@ -430,7 +430,7 @@ private fun EnhancedScannedModelsDialog(
if (showSearch || hasFreeModels) {
Column(Modifier.padding(horizontal = 8.dp)) {
if (showSearch) {
AppTextField(
AppOutlinedTextField(
value = searchQuery,
onValueChange = { searchQuery = it },
modifier = Modifier.fillMaxWidth(),
@@ -474,7 +474,7 @@ private fun EnhancedScannedModelsDialog(
)
}
} else {
itemsIndexed(filteredModels, key = { index, model -> "${model.modelId}_${model.providerKey}_${model.displayName}_$index" }) { index, model ->
itemsIndexed(filteredModels, key = { index, model -> "${model.modelId}_${model.providerKey}_${model.displayName}_$index" }) { _, model ->
var expanded by remember { mutableStateOf(false) }
var canExpand by remember { mutableStateOf(false) }
val secondary = buildString {

View File

@@ -71,9 +71,9 @@ import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppTabLayout
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppTopAppBar
import eu.gaudian.translator.view.composable.ClickableText
import eu.gaudian.translator.view.composable.PrimaryButton
@@ -1006,11 +1006,12 @@ private fun ApiKeyInput(
) {
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) {
val clipboard = LocalClipboardManager.current
AppTextField(
AppOutlinedTextField(
value = apiKey,
onValueChange = onApiKeyChanged,
label = { Text(stringResource(R.string.text_enter_api_key)) },
modifier = Modifier.fillMaxWidth(),
maxLines = 3,
trailingIcon = {
IconButton(
onClick = {
@@ -1066,20 +1067,20 @@ fun AddProviderDialog(onDismiss: () -> Unit, onConfirm: (ApiProvider) -> Unit) {
modifier = Modifier.padding(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
AppTextField(
AppOutlinedTextField(
value = displayName,
onValueChange = { newValue: String -> displayName = newValue },
label = { Text(stringResource(R.string.display_name) + " *") },
modifier = Modifier.fillMaxWidth()
)
AppTextField(
AppOutlinedTextField(
value = baseUrl,
onValueChange = { newValue: String -> baseUrl = newValue },
label = { Text(stringResource(R.string.text_base_url_and_example) + " *") },
modifier = Modifier.fillMaxWidth(),
maxLines = 2
)
AppTextField(
AppOutlinedTextField(
value = endpoint,
onValueChange = { newValue: String -> endpoint = newValue },
label = { Text(buildString {
@@ -1088,7 +1089,7 @@ fun AddProviderDialog(onDismiss: () -> Unit, onConfirm: (ApiProvider) -> Unit) {
}) },
modifier = Modifier.fillMaxWidth()
)
AppTextField(
AppOutlinedTextField(
value = websiteUrl,
onValueChange = { newValue: String -> websiteUrl = newValue },
label = { Text(buildString {
@@ -1189,19 +1190,19 @@ fun EditProviderDialog(
modifier = Modifier.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
AppTextField(
AppOutlinedTextField(
value = displayName,
onValueChange = { displayName = it },
label = { Text(stringResource(R.string.display_name) + " *") },
modifier = Modifier.fillMaxWidth()
)
AppTextField(
AppOutlinedTextField(
value = baseUrl,
onValueChange = { baseUrl = it },
label = { Text(stringResource(R.string.text_base_url_and_example) + " *") },
modifier = Modifier.fillMaxWidth()
)
AppTextField(
AppOutlinedTextField(
value = endpoint,
onValueChange = { endpoint = it },
label = { Text(buildString {
@@ -1210,7 +1211,7 @@ fun EditProviderDialog(
}) },
modifier = Modifier.fillMaxWidth()
)
AppTextField(
AppOutlinedTextField(
value = websiteUrl,
onValueChange = { websiteUrl = it },
label = { Text(buildString {
@@ -1324,14 +1325,14 @@ fun AddModelDialog(
}
// Manual entry
AppTextField(
AppOutlinedTextField(
value = displayName,
onValueChange = { newValue: String -> displayName = newValue },
label = { Text(stringResource(R.string.display_name)) },
enabled = !isLoading,
modifier = Modifier.fillMaxWidth()
)
AppTextField(
AppOutlinedTextField(
value = modelId,
onValueChange = { newValue: String -> modelId = newValue },
label = { Text(stringResource(R.string.model_id)) },
@@ -1339,7 +1340,7 @@ fun AddModelDialog(
modifier = Modifier.fillMaxWidth(),
maxLines = 2
)
AppTextField(
AppOutlinedTextField(
value = description,
onValueChange = { newValue: String -> description = newValue },
label = { Text(stringResource(R.string.description)) },
@@ -1429,19 +1430,19 @@ fun EditModelDialog(
modifier = Modifier.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
AppTextField(
AppOutlinedTextField(
value = displayName,
onValueChange = { displayName = it },
label = { Text(stringResource(R.string.display_name)) },
modifier = Modifier.fillMaxWidth()
)
AppTextField(
AppOutlinedTextField(
value = modelId,
onValueChange = { modelId = it },
label = { Text(stringResource(R.string.model_id)) },
modifier = Modifier.fillMaxWidth()
)
AppTextField(
AppOutlinedTextField(
value = description,
onValueChange = { description = it },
label = { Text(stringResource(R.string.description)) },

View File

@@ -42,7 +42,7 @@ import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedButton
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.ModelBadges
data class PromptSettingsState(
@@ -77,7 +77,7 @@ fun BasePromptSettingsScreen(
modifier = Modifier.fillMaxWidth(),
) {
Column(Modifier.padding(16.dp)) {
AppTextField(
AppOutlinedTextField(
value = state.customPrompt,
onValueChange = onPromptChanged,
modifier = Modifier.fillMaxWidth(),

View File

@@ -34,8 +34,8 @@ import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.LocalShowExperimentalFeatures
import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppTopAppBar
import eu.gaudian.translator.view.composable.OptionItemSwitch
import eu.gaudian.translator.view.composable.PrimaryButton
@@ -130,7 +130,7 @@ fun DictionaryOptionsScreen(
apiViewModel.setDictionaryModel(model)
},
)
AppTextField(
AppOutlinedTextField(
value = tempPrompt,
onValueChange = { tempPrompt = it },
label = { Text(stringResource(R.string.text_custom_dictionary_prompt)) },

View File

@@ -31,8 +31,8 @@ import eu.gaudian.translator.R
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppTopAppBar
import eu.gaudian.translator.view.composable.PrimaryButton
import eu.gaudian.translator.view.composable.SecondaryButton
@@ -114,7 +114,7 @@ fun ExerciseSettingsScreen(
apiViewModel.setExerciseModel(model)
},
)
AppTextField(
AppOutlinedTextField(
value = tempPrompt,
onValueChange = { tempPrompt = it },
label = { Text(stringResource(R.string.custom_exercise_prompt)) },

View File

@@ -20,7 +20,7 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedButton
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.CorrectButton
import eu.gaudian.translator.view.composable.WrongButton
@@ -43,7 +43,7 @@ fun ExerciseControls(
Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {
// Input field for spelling, only shown before the answer is revealed.
if (!isRevealed && state is VocabularyExerciseState.Spelling) {
AppTextField(
AppOutlinedTextField(
value = spellingAnswer,
onValueChange = { spellingAnswer = it },
label = { Text(stringResource(R.string.type_the_translation)) },

View File

@@ -68,8 +68,8 @@ import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.AppScaffold
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppTopAppBar
import eu.gaudian.translator.view.composable.SingleLanguageDropDown
import eu.gaudian.translator.view.dialogs.CategoryDropdown
@@ -455,7 +455,7 @@ fun VocabularySortingItem(
}
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
AppTextField(
AppOutlinedTextField(
value = wordFirst,
onValueChange = { wordFirst = it },
label = { Text(stringResource(R.string.label_word)) },
@@ -469,7 +469,7 @@ fun VocabularySortingItem(
}
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
AppTextField(
AppOutlinedTextField(
value = wordSecond,
onValueChange = { wordSecond = it },
label = { Text(stringResource(R.string.label_translation)) },

View File

@@ -45,7 +45,7 @@ import eu.gaudian.translator.model.grammar.formatGrammarDetails
import eu.gaudian.translator.utils.Log
import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.composable.AppDialog
import eu.gaudian.translator.view.composable.AppTextField
import eu.gaudian.translator.view.composable.AppOutlinedTextField
import eu.gaudian.translator.view.composable.DialogButton
import eu.gaudian.translator.viewmodel.LanguageConfigViewModel
@@ -185,7 +185,7 @@ private fun GuidedEditTabContent(
expanded = isCategoryDropdownExpanded,
onExpandedChange = { isCategoryDropdownExpanded = !isCategoryDropdownExpanded }
) {
AppTextField(
AppOutlinedTextField(
modifier = Modifier
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, enabled = true)
.fillMaxWidth(),
@@ -247,7 +247,7 @@ private fun DynamicField(
when (fieldConfig.type) {
"text" -> {
AppTextField(
AppOutlinedTextField(
value = currentValue ?: "",
onValueChange = { onValueChange(it.ifEmpty { null }) },
label = { Text(label) },
@@ -262,7 +262,7 @@ private fun DynamicField(
isExpanded = !isExpanded
}
) {
AppTextField(
AppOutlinedTextField(
modifier = Modifier
.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, enabled = true)
.fillMaxWidth(),