refactor BasePromptSettingsScreen to use InspiringSearchField and unify prompt settings across the app

This commit is contained in:
jonasgaudian
2026-02-13 16:54:24 +01:00
parent b5a9f5873a
commit f6fb6e77a8
10 changed files with 57 additions and 90 deletions

View File

@@ -134,7 +134,9 @@ fun AppOutlinedTextField(
fun InspiringSearchField( fun InspiringSearchField(
value: String, value: String,
hints : Array<String>, hints : Array<String>,
onValueChange: (String) -> Unit onValueChange: (String) -> Unit,
minLines: Int = 1,
maxLines: Int = 1
) { ) {
var currentHintIndex by remember { mutableIntStateOf(0) } var currentHintIndex by remember { mutableIntStateOf(0) }
@@ -166,14 +168,14 @@ fun InspiringSearchField(
Text( Text(
text = hint, text = hint,
color = Color.Gray.copy(alpha = 0.6f), color = Color.Gray.copy(alpha = 0.6f),
maxLines = 1, //maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
} }
}, },
singleLine = true, singleLine = true,
minLines = 1, minLines = minLines,
maxLines = 1 maxLines = maxLines
) )
} }

View File

@@ -153,7 +153,7 @@ fun AppCard(
if (!title.isNullOrBlank()) { if (!title.isNullOrBlank()) {
Text( Text(
text = title, text = title,
style = MaterialTheme.typography.headlineMedium, style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface color = MaterialTheme.colorScheme.onSurface
) )
} }

View File

@@ -1,7 +1,6 @@
package eu.gaudian.translator.view.settings package eu.gaudian.translator.view.settings
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -9,7 +8,6 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
@@ -32,6 +30,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R import eu.gaudian.translator.R
@@ -42,7 +41,7 @@ import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppCard import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedButton import eu.gaudian.translator.view.composable.AppOutlinedButton
import eu.gaudian.translator.view.composable.AppOutlinedTextField import eu.gaudian.translator.view.composable.InspiringSearchField
import eu.gaudian.translator.view.composable.ModelBadges import eu.gaudian.translator.view.composable.ModelBadges
data class PromptSettingsState( data class PromptSettingsState(
@@ -55,13 +54,14 @@ data class PromptSettingsState(
@Composable @Composable
fun BasePromptSettingsScreen( fun BasePromptSettingsScreen(
state: PromptSettingsState, state: PromptSettingsState,
providers: List<ApiProvider>, // Pass the list of providers providers: List<ApiProvider>,
description: String, description: String,
onPromptChanged: (String) -> Unit, onPromptChanged: (String) -> Unit,
onSaveClicked: () -> Unit, onSaveClicked: () -> Unit,
onModelSelected: (LanguageModel?) -> Unit, onModelSelected: (LanguageModel?) -> Unit,
onExamplePromptClicked: (String) -> Unit hints: Array<String>
) { ) {
AppCard {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -73,16 +73,15 @@ fun BasePromptSettingsScreen(
modifier = Modifier.padding(vertical = 16.dp) modifier = Modifier.padding(vertical = 16.dp)
) )
AppCard(
modifier = Modifier.fillMaxWidth(),
) {
Column(Modifier.padding(16.dp)) { Column(Modifier.padding(16.dp)) {
AppOutlinedTextField( InspiringSearchField(
value = state.customPrompt, value = state.customPrompt,
onValueChange = onPromptChanged, onValueChange = onPromptChanged,
modifier = Modifier.fillMaxWidth(), //label = { Text(stringResource(id = R.string.text_enter_your_custom_prompt)) },
label = { Text(stringResource(id = R.string.text_enter_your_custom_prompt)) }, minLines = 3,
minLines = 3 maxLines = 5,
hints = hints,
) )
Row( Row(
@@ -109,26 +108,9 @@ fun BasePromptSettingsScreen(
} }
} }
} }
}
Spacer(modifier = Modifier.height(24.dp))
Text( }
text = stringResource(R.string.text_example_prompts),
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
state.examplePrompts.forEach { prompt ->
Text(
text = prompt,
modifier = Modifier
.fillMaxWidth()
.clickable { onExamplePromptClicked(prompt) }
.padding(vertical = 8.dp)
)
HorizontalDivider()
}
} }
} }
@@ -274,7 +256,7 @@ fun ApiModelDropDown(
Text( Text(
text = providerName, text = providerName,
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
fontWeight = androidx.compose.ui.text.font.FontWeight.Medium, fontWeight = FontWeight.Medium,
color = if (isActive) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) color = if (isActive) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
) )
Text( Text(
@@ -305,7 +287,7 @@ fun ApiModelDropDown(
text = model.displayName, text = model.displayName,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
fontWeight = if (model == selectedModel) androidx.compose.ui.text.font.FontWeight.Medium else androidx.compose.ui.text.font.FontWeight.Normal fontWeight = if (model == selectedModel) FontWeight.Medium else FontWeight.Normal
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
ModelBadges( ModelBadges(
@@ -387,5 +369,6 @@ fun BasePromptSettingsScreenPreview() {
onPromptChanged = {}, onPromptChanged = {},
onSaveClicked = {}, onSaveClicked = {},
onModelSelected = {}, onModelSelected = {},
onExamplePromptClicked = {}) hints = listOf("test1", "test2").toTypedArray(),
)
} }

View File

@@ -15,6 +15,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringArrayResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -82,7 +83,7 @@ fun CustomVocabularyPromptScreen(
onPromptChanged = { tempPrompt = it }, onPromptChanged = { tempPrompt = it },
onSaveClicked = { settingsViewModel.saveCustomVocabularyPrompt(tempPrompt) }, onSaveClicked = { settingsViewModel.saveCustomVocabularyPrompt(tempPrompt) },
onModelSelected = { apiViewModel.setVocabularyModel(it) }, onModelSelected = { apiViewModel.setVocabularyModel(it) },
onExamplePromptClicked = { tempPrompt = it } hints = stringArrayResource(R.array.vocabulary_example_prompts),
) )
} }
} }

View File

@@ -4,14 +4,12 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement 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.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
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.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -34,11 +32,9 @@ import eu.gaudian.translator.utils.findActivity
import eu.gaudian.translator.view.LocalShowExperimentalFeatures import eu.gaudian.translator.view.LocalShowExperimentalFeatures
import eu.gaudian.translator.view.composable.AppCard import eu.gaudian.translator.view.composable.AppCard
import eu.gaudian.translator.view.composable.AppIcons 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.AppScaffold
import eu.gaudian.translator.view.composable.AppTopAppBar import eu.gaudian.translator.view.composable.AppTopAppBar
import eu.gaudian.translator.view.composable.OptionItemSwitch import eu.gaudian.translator.view.composable.OptionItemSwitch
import eu.gaudian.translator.view.composable.PrimaryButton
import eu.gaudian.translator.view.dictionary.DictionaryManagerContent import eu.gaudian.translator.view.dictionary.DictionaryManagerContent
import eu.gaudian.translator.view.hints.getDictionaryOptionsHint import eu.gaudian.translator.view.hints.getDictionaryOptionsHint
import eu.gaudian.translator.viewmodel.ApiViewModel import eu.gaudian.translator.viewmodel.ApiViewModel
@@ -113,51 +109,32 @@ fun DictionaryOptionsScreen(
} }
item { item {
AppCard { val screenState = PromptSettingsState(
Column( availableModels = availableModels,
modifier = Modifier.padding(16.dp), selectedModel = selectedModel,
verticalArrangement = Arrangement.spacedBy(16.dp) customPrompt = tempPrompt,
) { examplePrompts = emptyList()
Text( )
stringResource(R.string.text_ai_model_custom_prompt),
style = MaterialTheme.typography.titleMedium BasePromptSettingsScreen(
) state = screenState,
ApiModelDropDown( providers = allProviders,
models = availableModels, description = stringResource(R.string.text_description_dictionary_prompt),
providers = allProviders, onPromptChanged = { tempPrompt = it },
selectedModel = selectedModel, onSaveClicked = { settingsViewModel.saveCustomPromptDictionary(tempPrompt) },
onModelSelected = { model -> onModelSelected = { apiViewModel.setDictionaryModel(it) },
apiViewModel.setDictionaryModel(model) hints = emptyArray(), //TODO
}, )
)
AppOutlinedTextField(
value = tempPrompt,
onValueChange = { tempPrompt = it },
label = { Text(stringResource(R.string.text_custom_dictionary_prompt)) },
modifier = Modifier.defaultMinSize(minHeight = 120.dp)
)
PrimaryButton(
onClick = { settingsViewModel.saveCustomPromptDictionary(tempPrompt) },
text = stringResource(R.string.text_save_prompt),
modifier = Modifier.align(Alignment.End)
)
}
}
} }
item { item {
AppCard { AppCard(
AppCard (
title = stringResource(R.string.label_dictionary_content), title = stringResource(R.string.label_dictionary_content),
text = stringResource(R.string.text_select_the_content_dictionary), text = stringResource(R.string.text_select_the_content_dictionary),
expandable = true, expandable = true,
initiallyExpanded = false, initiallyExpanded = false,
) {
){
Column(Modifier.padding(0.dp)) { Column(Modifier.padding(0.dp)) {
dictionaryOptionKeys.zip(dictionaryOptionLabels).forEach { (key, label) -> dictionaryOptionKeys.zip(dictionaryOptionLabels).forEach { (key, label) ->
val isChecked = dictionarySwitches.contains(key) || dictionarySwitches.contains(label) val isChecked = dictionarySwitches.contains(key) || dictionarySwitches.contains(label)
@@ -182,7 +159,6 @@ fun DictionaryOptionsScreen(
} }
} }
} }
}
} }
item { item {

View File

@@ -104,11 +104,11 @@ fun TranslationSettingsScreen(
BasePromptSettingsScreen( BasePromptSettingsScreen(
state = screenState, state = screenState,
providers = allProviders, providers = allProviders,
description = stringResource(R.string.text_here_you_can_set), description = stringResource(R.string.text_translation_instructions),
onPromptChanged = { tempPrompt = it }, onPromptChanged = { tempPrompt = it },
onSaveClicked = { settingsViewModel.saveCustomPrompt(tempPrompt) }, onSaveClicked = { settingsViewModel.saveCustomPrompt(tempPrompt) },
onModelSelected = { apiViewModel.setTranslationModel(it) }, onModelSelected = { apiViewModel.setTranslationModel(it) },
onExamplePromptClicked = { tempPrompt = it } hints = context.resources.getStringArray(R.array.example_prompts),
) )
} }
} }

View File

@@ -155,11 +155,9 @@
<string name="text_copy_corrected_text">Korrigierten Text kopieren</string> <string name="text_copy_corrected_text">Korrigierten Text kopieren</string>
<string name="label_correct">Korrekt</string> <string name="label_correct">Korrekt</string>
<string name="text_explanation">Erklärung</string> <string name="text_explanation">Erklärung</string>
<string name="text_here_you_can_set">Hier kannst du einen benutzerdefinierten Prompt für das KI-Übersetzungsmodell festlegen, um den Übersetzungsstil anzupassen.</string>
<string name="text_vocabulary_prompt">Vokabular-Prompt</string> <string name="text_vocabulary_prompt">Vokabular-Prompt</string>
<string name="text_here_you_can_set_a_custom_">Hier kannst du einen benutzerdefinierten Prompt festlegen, um zu definieren, wie neue Vokabeln erstellt werden.</string> <string name="text_here_you_can_set_a_custom_">Hier kannst du einen benutzerdefinierten Prompt festlegen, um zu definieren, wie neue Vokabeln erstellt werden.</string>
<string name="text_select_the_content_dictionary">Wähle die Inhalte aus, die für einen Wörterbucheintrag erstellt werden sollen.</string> <string name="text_select_the_content_dictionary">Wähle die Inhalte aus, die für einen Wörterbucheintrag erstellt werden sollen.</string>
<string name="text_ai_model_custom_prompt"><![CDATA[KI-Modell & Prompt]]></string>
<string name="text_custom_dictionary_prompt">Benutzerdefinierter Wörterbuch-Prompt</string> <string name="text_custom_dictionary_prompt">Benutzerdefinierter Wörterbuch-Prompt</string>
<string name="text_save_prompt">Prompt speichern</string> <string name="text_save_prompt">Prompt speichern</string>
<string name="text_light">Hell</string> <string name="text_light">Hell</string>

View File

@@ -153,11 +153,9 @@
<string name="text_copy_corrected_text">Copiar texto corrigido</string> <string name="text_copy_corrected_text">Copiar texto corrigido</string>
<string name="label_correct">Correto</string> <string name="label_correct">Correto</string>
<string name="text_explanation">Explicação</string> <string name="text_explanation">Explicação</string>
<string name="text_here_you_can_set">Aqui você pode definir um prompt personalizado para o modelo de tradução de IA, ajustando o estilo da tradução.</string>
<string name="text_vocabulary_prompt">Prompt de Vocabulário</string> <string name="text_vocabulary_prompt">Prompt de Vocabulário</string>
<string name="text_here_you_can_set_a_custom_">Aqui você pode definir um prompt personalizado para definir como novos itens de vocabulário são gerados.</string> <string name="text_here_you_can_set_a_custom_">Aqui você pode definir um prompt personalizado para definir como novos itens de vocabulário são gerados.</string>
<string name="text_select_the_content_dictionary">Selecione o conteúdo a ser gerado para uma entrada de dicionário.</string> <string name="text_select_the_content_dictionary">Selecione o conteúdo a ser gerado para uma entrada de dicionário.</string>
<string name="text_ai_model_custom_prompt"><![CDATA[Modelo de IA & Prompt]]></string>
<string name="text_custom_dictionary_prompt">Prompt Personalizado do Dicionário</string> <string name="text_custom_dictionary_prompt">Prompt Personalizado do Dicionário</string>
<string name="text_save_prompt">Salvar Prompt</string> <string name="text_save_prompt">Salvar Prompt</string>
<string name="text_light">Claro</string> <string name="text_light">Claro</string>

View File

@@ -49,12 +49,21 @@
<item>Idiomatic expressions</item> <item>Idiomatic expressions</item>
</string-array> </string-array>
<string-array name="vocabulary_example_prompts" >
<item>Use Latin American Spanish</item>
<item>Avoid long words</item>
<item>Avoid sentences</item>
<item>Include many verbs and adjectives</item>
<item>Use informal language</item>
</string-array>
<string-array name="changelog_entries"> <string-array name="changelog_entries">
<item>Version 0.3.0 \n• Enabled CSV Import for Vocabulary\n• Option to use a translation server for translations instead of AI models for some supported langugaes\n• UI bug fixes \n• Show word frequency \n• Performance optimizations \n• Improved translations (German and Portuguese)</item> <item>Version 0.3.0 \n• Enabled CSV Import for Vocabulary\n• Option to use a translation server for translations instead of AI models for some supported langugaes\n• UI bug fixes \n• Show word frequency \n• Performance optimizations \n• Improved translations (German and Portuguese)</item>
<item>Version 0.4.0 \n• Added dictionary download (beta) \n• UI enhancements \n• Bugfixes \n• Re-designed vocabulary card with improved UI \n• More pre-configured providers \n• Improved performance</item> <item>Version 0.4.0 \n• Added dictionary download (beta) \n• UI enhancements \n• Bugfixes \n• Re-designed vocabulary card with improved UI \n• More pre-configured providers \n• Improved performance</item>
</string-array> </string-array>
</resources> </resources>

View File

@@ -713,7 +713,6 @@
<string name="text_add_to_favorites">Add to favorites</string> <string name="text_add_to_favorites">Add to favorites</string>
<string name="text_ai_failed_to_create_the_exercise">AI failed to create the exercise.</string> <string name="text_ai_failed_to_create_the_exercise">AI failed to create the exercise.</string>
<string name="text_ai_generation_failed_with_an_exception">AI generation failed with an exception</string> <string name="text_ai_generation_failed_with_an_exception">AI generation failed with an exception</string>
<string name="text_ai_model_custom_prompt"><![CDATA[AI Model & Custom Prompt]]></string>
<string name="text_all_dictionaries_deleted_successfully">All dictionaries deleted successfully</string> <string name="text_all_dictionaries_deleted_successfully">All dictionaries deleted successfully</string>
<string name="text_all_items_completed">All items completed!</string> <string name="text_all_items_completed">All items completed!</string>
<string name="text_all_languages">All Languages</string> <string name="text_all_languages">All Languages</string>
@@ -833,7 +832,7 @@
<string name="text_generate_exercise_with_ai">Generate Exercise with AI</string> <string name="text_generate_exercise_with_ai">Generate Exercise with AI</string>
<string name="text_generating_questions_from_video">Generating questions from video…</string> <string name="text_generating_questions_from_video">Generating questions from video…</string>
<string name="text_get_api_key_at">Get API Key at %1$s</string> <string name="text_get_api_key_at">Get API Key at %1$s</string>
<string name="text_here_you_can_set">Here you can set a custom prompt for the AI translation model. This allows you to fine-tune the translation style.</string> <string name="text_translation_instructions">Set model for translation and give optional instructions on how to translate.</string>
<string name="text_here_you_can_set_a_custom_">Here you can set a custom prompt for the AI vocabulary model. This allows you to define how new vocabulary entries are generated.</string> <string name="text_here_you_can_set_a_custom_">Here you can set a custom prompt for the AI vocabulary model. This allows you to define how new vocabulary entries are generated.</string>
<string name="text_hint">Hint</string> <string name="text_hint">Hint</string>
<string name="text_hint_you_can_search">Hint: You can search for any term, e.g. \"Things to do at the zoo\" or \"irregular verbs\"!</string> <string name="text_hint_you_can_search">Hint: You can search for any term, e.g. \"Things to do at the zoo\" or \"irregular verbs\"!</string>
@@ -1050,4 +1049,5 @@
<string name="label_regenerate">Regenerate</string> <string name="label_regenerate">Regenerate</string>
<string name="label_read_aloud">Read Aloud</string> <string name="label_read_aloud">Read Aloud</string>
<string name="label_all_categories">All Categories</string> <string name="label_all_categories">All Categories</string>
<string name="text_description_dictionary_prompt">Set a model for generating dictionary content and give optional instructions.</string>
</resources> </resources>