From d2e77083adb0928947e13200b88dc9604d19780c Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:15:26 +0100 Subject: [PATCH] migrate hints system to a localized markdown-based architecture and refactor related UI components --- app/src/main/assets/hints/api_key_hint.md | 53 +++ app/src/main/assets/hints/category_hint.md | 49 +++ app/src/main/assets/hints/dictionary_hint.md | 49 +++ app/src/main/assets/hints/import_hint.md | 53 +++ .../main/assets/hints/learning_stages_hint.md | 53 +++ app/src/main/assets/hints/review_hint.md | 63 ++++ app/src/main/assets/hints/sorting_hint.md | 72 ++++ app/src/main/assets/hints/translation_hint.md | 67 ++++ .../assets/hints/vocabulary_progress_hint.md | 78 +++++ .../view/composable/AppTopAppBar.kt | 7 +- .../view/dialogs/ImportVocabularyDialog.kt | 4 +- .../view/dialogs/VocabularyReviewScreen.kt | 2 +- .../translator/view/hints/AddModelScanHint.kt | 136 +------- .../translator/view/hints/ApiKeyHints.kt | 129 +------ .../translator/view/hints/CategoryHint.kt | 116 +------ .../view/hints/CategoryHintScreen.kt | 129 +------ .../view/hints/DictionaryOptionsHint.kt | 68 +--- .../view/hints/ExampleModernHint.kt | 161 +-------- .../eu/gaudian/translator/view/hints/Hint.kt | 251 -------------- .../translator/view/hints/HintElement.kt | 315 ++++++------------ .../translator/view/hints/HintScreens.kt | 34 +- .../view/hints/HintsOverviewScreen.kt | 29 +- .../view/hints/ImportVocabularyHints.kt | 152 +-------- .../view/hints/LearningStagesHint.kt | 198 +---------- .../view/hints/MarkdownHintLoader.kt | 225 +++++++++++++ .../view/hints/SortingScreenHint.kt | 247 +------------- .../view/hints/TranslationScreenHint.kt | 88 +---- .../view/hints/VocabularyProgressHint.kt | 120 +------ .../view/settings/AddModelScreen.kt | 3 +- .../translator/view/settings/ApiKeyScreen.kt | 4 +- .../view/settings/CustomPromptScreens.kt | 8 +- .../view/settings/DictionaryOptionsScreen.kt | 2 +- .../view/settings/TranslationSettings.kt | 7 +- .../VocabularyProgressOptionsScreen.kt | 3 +- .../vocabulary/VocabularySortingScreen.kt | 4 +- 35 files changed, 1035 insertions(+), 1944 deletions(-) create mode 100644 app/src/main/assets/hints/api_key_hint.md create mode 100644 app/src/main/assets/hints/category_hint.md create mode 100644 app/src/main/assets/hints/dictionary_hint.md create mode 100644 app/src/main/assets/hints/import_hint.md create mode 100644 app/src/main/assets/hints/learning_stages_hint.md create mode 100644 app/src/main/assets/hints/review_hint.md create mode 100644 app/src/main/assets/hints/sorting_hint.md create mode 100644 app/src/main/assets/hints/translation_hint.md create mode 100644 app/src/main/assets/hints/vocabulary_progress_hint.md create mode 100644 app/src/main/java/eu/gaudian/translator/view/hints/MarkdownHintLoader.kt diff --git a/app/src/main/assets/hints/api_key_hint.md b/app/src/main/assets/hints/api_key_hint.md new file mode 100644 index 0000000..a1710ca --- /dev/null +++ b/app/src/main/assets/hints/api_key_hint.md @@ -0,0 +1,53 @@ +# How to Connect to an AI Model + +This guide explains how to connect your app to an AI model using an API key. + +## Getting Started + +To use AI models in your app, you need to provide a valid API key. This key authenticates your requests and tracks your usage. + +> **Note:** Keep your API key secure and never share it publicly. + +## Key Status Indicators + +Your API key can be in one of two states: + +| Status | Icon | Meaning | +|--------|------|---------| +| Active | ✅ | Key is valid and working | +| Missing | ⚠️ | Key is not set or was cleared | + +### Active Key + +When your API key is active, you can use all available AI models. The system will display a checkmark indicator next to the key status. + +### Missing Key + +If the key is missing or cleared, you won't be able to make API requests. You'll see a warning indicator, and any attempt to use AI features will prompt you to add a valid key. + +## Connecting Your AI Model + +1. **Navigate to Settings** → API Key section +2. **Enter your API key** in the provided field +3. **Save** the configuration +4. **Verify** the connection is successful + +## Troubleshooting + +If you're having issues with your API key: + +1. **Verify the key is correct** - Check for typos or extra spaces +2. **Ensure the key has proper permissions** - Some models require additional access +3. **Check your quota** - You may have exceeded your usage limits +4. **Try regenerating the key** - If all else fails, generate a new key from your provider + +```json +// Example API key format +{ + "api_key": "sk-xxxxxxxxxxxxxxxxxxxx" +} +``` + +--- + +**Need more help?** Check our documentation or contact support. diff --git a/app/src/main/assets/hints/category_hint.md b/app/src/main/assets/hints/category_hint.md new file mode 100644 index 0000000..6d59c9a --- /dev/null +++ b/app/src/main/assets/hints/category_hint.md @@ -0,0 +1,49 @@ +# Understanding Categories + +Learn how to use categories to organize and filter your vocabulary effectively. + +## What Are Categories? + +Categories help you organize your vocabulary into meaningful groups. You can use them to track words by topic, difficulty, or any custom system that works for you. + +## Two Types of Categories + +### List Categories + +List categories are simple groupings of vocabulary items. Words in a list category stay together regardless of their learning stage. + +**Use cases:** +- Group words by topic (e.g., "Food", "Travel", "Business") +- Create custom decks for specific purposes +- Organize words by source (e.g., "Book: Harry Potter") + +### Filter Categories + +Filter categories automatically include all vocabulary items that match certain criteria. Words are dynamically added based on the filter rules. + +**Use cases:** +- Filter by learning stage (e.g., "Words I'm learning") +- Filter by mastery level (e.g., "Words I need to review") +- Combine multiple criteria for complex filtering + +## Creating Categories + +1. **Tap the + button** to create a new category +2. **Choose the type** - List or Filter +3. **Add a name** and optional description +4. **Set the rules** (for filter categories) +5. **Save** your category + +## Managing Categories + +- **Edit** - Tap a category to modify its settings +- **Delete** - Swipe left and tap delete (words are not deleted) +- **Reorder** - Drag to change display order + +## Tips + +> **Pro Tip:** Use filter categories for learning stages to automatically track progress across all words at a certain level. + +--- + +*Need more help? Check our documentation.* diff --git a/app/src/main/assets/hints/dictionary_hint.md b/app/src/main/assets/hints/dictionary_hint.md new file mode 100644 index 0000000..151a7e1 --- /dev/null +++ b/app/src/main/assets/hints/dictionary_hint.md @@ -0,0 +1,49 @@ +# Dictionary Options + +Learn how to configure and use the dictionary options for better translations. + +## What Are Dictionary Options? + +Dictionary options allow you to customize how translations appear and how the dictionary feature works. + +## Key Features + +### Synonyms + +Enable synonyms to see alternative translations: +- Toggle the **Synonyms** switch +- View multiple translation options +- Choose the most appropriate meaning + +### Part of Speech + +Display grammatical information: +- See if a word is noun, verb, adjective +- Helps understand context +- Available for supported languages + +### Example Sentences + +View usage examples: +- See words in context +- Learn proper usage patterns +- Understand nuances + +## Configuration + +### Enable/Disable Features + +1. Go to Settings → Dictionary +2. Toggle desired options on/off +3. Changes apply immediately + +### Custom Dictionary + +Add custom entries: +- Tap the **+** button +- Enter word and translation +- Save to your personal dictionary + +--- + +*Tip: Enable all options for the richest translation experience!* diff --git a/app/src/main/assets/hints/import_hint.md b/app/src/main/assets/hints/import_hint.md new file mode 100644 index 0000000..03e45dc --- /dev/null +++ b/app/src/main/assets/hints/import_hint.md @@ -0,0 +1,53 @@ +# Import Vocabulary with AI + +Generate vocabulary lists automatically using AI assistance. + +## Getting Started + +Use AI to quickly create vocabulary lists from your learning goals. + +## Step-by-Step Guide + +### Step 1: Enter Search Term + +Type a topic, theme, or concept for your vocabulary list: +- Be specific for better results +- Example: "German food and restaurant phrases" +- Example: "Business vocabulary for meetings" + +### Step 2: Select Languages + +Choose source and target languages: +- **Source language** - The language you're learning from +- **Target language** - Your native language + +### Step 3: Set Amount + +Choose how many words to generate: +- Slide to select 1-25 words +- More words = longer processing time +- Start small for your first import + +### Step 4: Generate + +Tap the generate button: +- AI creates the vocabulary list +- Review each entry before saving +- Edit any translations if needed + +## After Generation + +Once generated, you can: + +- **Review** - Check each word-translation pair +- **Edit** - Correct any mistakes +- **Delete** - Remove unwanted entries +- **Import All** - Add all to your vocabulary + +## Tips + +> **Pro Tip:** Start with 10 words per import to get familiar with the feature. + +--- + +*Need help? Check our vocabulary management guide.* diff --git a/app/src/main/assets/hints/learning_stages_hint.md b/app/src/main/assets/hints/learning_stages_hint.md new file mode 100644 index 0000000..57a6346 --- /dev/null +++ b/app/src/main/assets/hints/learning_stages_hint.md @@ -0,0 +1,53 @@ +# Learning Stages + +Understand how vocabulary progresses through different learning stages to optimize your study sessions. + +## The Learning Stages + +Your vocabulary items move through these stages as you learn: + +| Stage | Name | Interval | Description | +|-------|------|----------|-------------| +| 🌟 | New | - | Just added vocabulary | +| 📅 | Stage 1 | 1 day | Recently learned | +| 📅 | Stage 2 | 3 days | Reinforcement | +| 📅 | Stage 3 | 1 week | Consolidation | +| 📅 | Stage 4 | 2 weeks | Deep learning | +| 📅 | Stage 5 | 1 month | Mastery | +| ✅ | Learned | ∞ | Fully learned | + +## How It Works + +### Answer Correctly ✅ + +When you correctly identify a word during review: +- The word **moves forward** to the next stage +- The interval until next review **increases** +- This helps you focus on words that need more practice + +### Answer Incorrectly ❌ + +When you make a mistake: +- The word **moves back** one or more stages +- The review interval **decreases** +- This ensures you practice challenging words more often + +## Customization + +All intervals and rules can be customized in Settings: + +- **Adjust intervals** for each stage +- **Change how many stages** to regress on errors +- **Skip stages** for certain word types +- **Enable/disable** specific stages + +## Visual Progress + +The app displays your progress visually: +- Stage indicators show current status +- Progress bars track advancement +- Statistics display overall mastery + +--- + +*Tip: Consistent daily practice is key to moving words through all stages!* diff --git a/app/src/main/assets/hints/review_hint.md b/app/src/main/assets/hints/review_hint.md new file mode 100644 index 0000000..535f00c --- /dev/null +++ b/app/src/main/assets/hints/review_hint.md @@ -0,0 +1,63 @@ +# Review Vocabulary + +Master your vocabulary through systematic review sessions. + +## The Review Screen + +When you start a review session, you'll see: + +- The **source word** to translate +- Options to reveal the **translation** +- Buttons to indicate your **knowledge level** + +## How to Review + +### 1. View the Word + +Read the source word carefully: +- Pay attention to the spelling +- Think of the meaning +- Recall the translation + +### 2. Reveal Translation + +Tap to show the translation: +- Compare with your recall +- Note any differences +- Learn from mistakes + +### 3. Rate Your Knowledge + +Rate how well you knew the answer: + +| Button | Meaning | Action | +|--------|---------|--------| +| 😓 | Hard | Moves back stages | +| 🤔 | Okay | Stays current | +| ✅ | Easy | Advances stage | + +## Review Statistics + +Track your progress: +- **Cards reviewed** - Total in session +- **Accuracy** - Percentage correct +- **Time spent** - Learning duration + +## Best Practices + +### Daily Reviews + +- Review **every day** for best results +- Complete **all due** cards before adding new +- Focus on **problem areas** + +### Spaced Repetition + +The system uses spaced repetition: +- **Hard cards** - Review sooner (1-2 days) +- **Okay cards** - Review normally (as scheduled) +- **Easy cards** - Review later (weeks) + +--- + +*Consistent daily review is the key to long-term retention!* diff --git a/app/src/main/assets/hints/sorting_hint.md b/app/src/main/assets/hints/sorting_hint.md new file mode 100644 index 0000000..cb77dc1 --- /dev/null +++ b/app/src/main/assets/hints/sorting_hint.md @@ -0,0 +1,72 @@ +# Sorting Vocabulary + +Learn how to efficiently sort and organize new vocabulary as you add them. + +## The Sorting Screen + +When you import vocabulary, you'll see the sorting screen where you can: + +- Review each word-translation pair +- Decide the next action for each item +- Handle duplicates and conflicts + +## Actions + +### ✅ Mark as Learned + +Move the word directly to Stage 1: +- The word enters your learning queue +- You'll review it according to the learning schedule + +### 🗑️ Delete + +Remove the word entirely: +- Use for duplicates or unwanted entries +- This action is permanent + +### 📝 Edit + +Tap on any word or translation to edit: +- Correct typos +- Improve translations +- Add additional context + +## Duplicate Handling + +When duplicates are detected: + +| Icon | Meaning | +|------|---------| +| ⚠️ | Duplicate detected | +| ✅ | Original entry | +| ❌ | Duplicate entry | + +**Options for duplicates:** +- Keep only the original +- Keep the newer entry +- Keep both (merge) +- Delete the duplicate + +## Helper Features + +### Remove Articles + +Toggle to automatically strip articles from words: +- "der Hund" → "Hund" +- "the dog" → "dog" +- Useful for cleaner vocabulary lists + +### Quick Actions + +Use quick action buttons for bulk operations: +- **Skip All** - Review later +- **Learn All** - Add all to Stage 1 +- **Delete Duplicates** - Auto-remove duplicates + +## Tips + +> **Pro Tip:** Review carefully before sorting. Once sorted, you can still edit words in the vocabulary list. + +--- + +*For more tips, check our vocabulary management guide.* diff --git a/app/src/main/assets/hints/translation_hint.md b/app/src/main/assets/hints/translation_hint.md new file mode 100644 index 0000000..cb0ba44 --- /dev/null +++ b/app/src/main/assets/hints/translation_hint.md @@ -0,0 +1,67 @@ +# Translation Features + +Discover the powerful translation capabilities of this app. + +## Alternative Translations + +Sometimes a word has multiple meanings: +- Tap the **Show alternatives** option +- See all possible translations +- Choose the most appropriate context + +## Custom Prompts + +Customize how translations are generated: + +### Create Custom Prompt + +1. Go to Settings → Translation +2. Tap **Add Custom Prompt** +3. Write your prompt template +4. Save and use in translations + +### Example Prompts + +``` +Translate {word} as used in {context} + +Provide formal translation of: {word} + +Casual translation for: {word} +``` + +## Multiple Translation Services + +Use different translation backends: + +| Service | Best For | Languages | +|---------|----------|-----------| +| AI Models | Context-aware | Many | +| Dictionary | Quick lookup | Limited | +| Online API | Accuracy | All | + +## Translation History + +Track all your translations: +- Automatically saved +- Search by word or date +- Export for review + +## Text-to-Speech (TTS) + +Hear words pronounced: +- Tap the **speaker icon** +- Choose voice for each language +- Adjust speed as needed + +## Quick Actions + +Fast access to common tasks: + +- **Copy** - Copy translation to clipboard +- **Add** - Add directly to vocabulary +- **Share** - Send to other apps + +--- + +*Pro Tip: Use custom prompts for domain-specific vocabulary!* diff --git a/app/src/main/assets/hints/vocabulary_progress_hint.md b/app/src/main/assets/hints/vocabulary_progress_hint.md new file mode 100644 index 0000000..df9e164 --- /dev/null +++ b/app/src/main/assets/hints/vocabulary_progress_hint.md @@ -0,0 +1,78 @@ +# Vocabulary Progress Tracking + +Monitor your vocabulary learning journey with detailed progress statistics. + +## Progress Overview + +Track your learning with these key metrics: + +### Words Learned + +- Total words added to your vocabulary +- Words currently in each learning stage +- Words marked as fully learned + +### Learning Streak + +- Days since you started learning +- Current streak count +- Best streak achieved + +### Review Statistics + +- Words reviewed today +- Accuracy rate per session +- Words due for review + +## Progress Tracking Features + +### 📊 Dashboard + +View your overall progress at a glance: +- Total vocabulary count +- Mastery percentage +- Recent activity summary + +### 📈 Statistics + +Detailed analytics include: +- Learning rate over time +- Stage distribution +- Accuracy trends +- Time spent studying + +### 🎯 Goals + +Set and track learning goals: +- Daily word targets +- Weekly review quotas +- Mastery milestones + +## Learning Stages Summary + +| Stage | Count | Percentage | +|-------|-------|------------| +| New | X | X% | +| Learning | X | X% | +| Mastered | X | X% | + +## Review System + +The review system helps you: + +1. **Prioritize** - Shows words due for review first +2. **Space** - Optimizes review timing for retention +3. **Track** - Records your performance over time + +## Customization + +Customize your progress tracking: + +- **Select metrics** to display on dashboard +- **Set goals** for personalized targets +- **Export data** for external analysis +- **Reset progress** if starting fresh + +--- + +*Keep practicing consistently to see your progress grow!* diff --git a/app/src/main/java/eu/gaudian/translator/view/composable/AppTopAppBar.kt b/app/src/main/java/eu/gaudian/translator/view/composable/AppTopAppBar.kt index 5c9a491..1da881e 100644 --- a/app/src/main/java/eu/gaudian/translator/view/composable/AppTopAppBar.kt +++ b/app/src/main/java/eu/gaudian/translator/view/composable/AppTopAppBar.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import eu.gaudian.translator.R import eu.gaudian.translator.ui.theme.ThemePreviews +import eu.gaudian.translator.view.hints.Hint import eu.gaudian.translator.view.hints.HintBottomSheet import eu.gaudian.translator.view.hints.LocalShowHints @@ -41,7 +42,7 @@ fun AppTopAppBar( navigationIcon: @Composable (() -> Unit)? = null, actions: @Composable RowScope.() -> Unit = {}, colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(), - hintContent: @Composable (() -> Unit)? = null + hintContent: Hint? = null ) { val sheetState = rememberModalBottomSheetState() var showBottomSheet by remember { mutableStateOf(false) } @@ -111,7 +112,9 @@ fun AppTopAppBar( showBottomSheet = false }, sheetState = sheetState, - content = hintContent + content = { + hintContent?.Render() + } ) } } diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/ImportVocabularyDialog.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/ImportVocabularyDialog.kt index 5aaca35..9ba1544 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/ImportVocabularyDialog.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/ImportVocabularyDialog.kt @@ -44,7 +44,7 @@ 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 +import eu.gaudian.translator.view.hints.ImportVocabularyHint import eu.gaudian.translator.viewmodel.LanguageViewModel import eu.gaudian.translator.viewmodel.VocabularyViewModel import kotlinx.coroutines.launch @@ -109,7 +109,7 @@ fun ImportDialogContent( AppDialog( onDismissRequest = onDismiss, title = { Text(descriptionText) }, - hintContent = { getImportVocabularyHint() }, + hintContent = { ImportVocabularyHint() }, content = { Column( modifier = Modifier diff --git a/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyReviewScreen.kt b/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyReviewScreen.kt index e9ebaf2..1fba26d 100644 --- a/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyReviewScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/dialogs/VocabularyReviewScreen.kt @@ -69,7 +69,7 @@ fun VocabularyReviewScreen( topBar = { AppTopAppBar( title = { Text(stringResource(R.string.found_items)) }, - hintContent = { getVocabularyReviewHint() } + hintContent = getVocabularyReviewHint() ) }, ) { paddingValues -> diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/AddModelScanHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/AddModelScanHint.kt index 5e09ffb..6d35318 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/AddModelScanHint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/AddModelScanHint.kt @@ -1,145 +1,21 @@ package eu.gaudian.translator.view.hints -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SuggestionChip -import androidx.compose.material3.SuggestionChipDefaults -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import eu.gaudian.translator.R -import eu.gaudian.translator.view.composable.AppIcons /** - * Transformed AddModelScanHint using the new uniform Hint structure. + * Migrated AddModelScanHint using markdown-based format. + * Content is loaded from localized assets/hints based on device locale. */ - @Composable fun getAddModelScanHint(): Hint { return Hint( - titleRes = R.string.hint_scan_hint_title, - elements = listOf( - - HintElement.Section( - title = stringResource(R.string.scan_hint_section_how_scan_works), - content = listOf( - HintElement.Text(stringResource(R.string.scan_hint_how_scan_works_paragraph)), - HintElement.UIElement { Spacer(Modifier.height(8.dp)) }, - HintElement.UIElement { ReusedScanButtonPreview() }, - HintElement.UIElement { Spacer(Modifier.height(8.dp)) }, - HintElement.BulletList( - listOf( - stringResource(R.string.scan_hint_bullet_results_depend), - stringResource(R.string.scan_hint_bullet_public_private), - stringResource(R.string.scan_hint_bullet_try_again) - ) - ) - ) - ), - HintElement.Divider, - HintElement.Section( - title = stringResource(R.string.scan_hint_section_why_missing), - content = listOf( - HintElement.InfoBadge( - icon = AppIcons.Lock, - text = stringResource(R.string.scan_hint_badge_restricted) - ), - HintElement.InfoBadge( - icon = AppIcons.Warning, - text = stringResource(R.string.scan_hint_badge_not_suitable) - ), - HintElement.InfoBadge( - icon = AppIcons.CheckCircle, - text = stringResource(R.string.scan_hint_badge_only_text_models) - ), - HintElement.Divider, - HintElement.Text(stringResource(R.string.scan_hint_focus_text_models)), - HintElement.Divider, - HintElement.Card { - PerformanceTierChips() - }, - HintElement.Divider, - HintElement.InfoBadge( - icon = AppIcons.Info, - text = stringResource(R.string.scan_hint_most_tasks_small_models) - ) - ) - ), - HintElement.Divider, - HintElement.Section( - title = stringResource(R.string.scan_hint_section_tips), - content = listOf( - HintElement.BulletList( - listOf( - stringResource(R.string.scan_hint_tip_verify_key), - stringResource(R.string.scan_hint_tip_select_org), - stringResource(R.string.scan_hint_tip_type_manually), - stringResource(R.string.scan_hint_tip_instruct_chat_text) - ) - ) - ) - ), - HintElement.Divider, - HintElement.Section( - title = stringResource(R.string.hint_scan_hint_section_visual_guide), - content = listOf( - HintElement.VisualStep( - step = stringResource(R.string.hint_scan_hint_step_1), - title = stringResource(R.string.hint_scan_hint_step1_title), - description = stringResource(R.string.hint_scan_hint_step1_desc), - trailing = { - Icon(AppIcons.Search, contentDescription = null, tint = MaterialTheme.colorScheme.primary) - } - ), - HintElement.VisualStep( - step = stringResource(R.string.hint_scan_hint_step_2), - title = stringResource(R.string.hint_scan_hint_step2_title), - description = stringResource(R.string.hint_scan_hint_step2_desc), - trailing = { - SuggestionChip( - onClick = {}, - label = { Text(stringResource(R.string.hint_scan_hint_label_text_chat)) }, - enabled = false, - icon = {}, - colors = SuggestionChipDefaults.suggestionChipColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - labelColor = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - } - ), - HintElement.VisualStep( - step = stringResource(R.string.hint_scan_hint_step_3), - title = stringResource(R.string.hint_scan_hint_step3_title), - description = stringResource(R.string.hint_scan_hint_step3_desc), - trailing = { - SmallPrimaryCard(text = stringResource(R.string.hint_scan_hint_add_validate)) - } - ) - ) - ), - HintElement.Divider, - HintElement.Section( - title = stringResource(R.string.hint_scan_hint_section_cant_find), - content = listOf( - HintElement.Text(stringResource(R.string.hint_scan_hint_manual_add_paragraph)) - ) + titleRes = R.string.hint_scan_hint_title, + elements = listOf( + HintElement.LocalizedMarkdown("example_hint") ) ) -) -} - - - -@Preview -@Composable -fun ReusedScanButtonPreviewPreview() { - ReusedScanButtonPreview() } @Preview @@ -151,4 +27,4 @@ fun AddModelScanHintPreview() { @Composable fun AddModelScanHint() { getAddModelScanHint().Render() -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/ApiKeyHints.kt b/app/src/main/java/eu/gaudian/translator/view/hints/ApiKeyHints.kt index a25ab61..effb0c5 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/ApiKeyHints.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/ApiKeyHints.kt @@ -1,130 +1,33 @@ package eu.gaudian.translator.view.hints -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import eu.gaudian.translator.R -import eu.gaudian.translator.view.composable.AppIcons /** - * TODO: Migrate to markdown-based hint format. - * Create a .md file in assets/hints/ and use MarkdownHint composable instead. - * See MarkdownHint.kt for implementation details. + * Migrated API Key hint using markdown-based format. + * Content is loaded from localized assets/hints based on device locale. */ -object ApiKeyHint: LegacyHint() { - override val titleRes: Int = R.string.hint_how_to_connect_to_an_ai - - @Composable - override fun getTitle(): String = stringResource(R.string.hint_how_to_connect_to_an_ai) - - @Composable - override fun Content() { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - HeaderWithIcon( - title = stringResource(R.string.hint_how_to_connect_to_an_ai) - ) - - HintSection(title = stringResource(R.string.connecting_your_ai_model)) { - Text( - text = stringResource(R.string.api_hint_intro_1), - style = MaterialTheme.typography.bodyMedium - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(R.string.api_hint_intro_2), - style = MaterialTheme.typography.bodyMedium - ) - } - - HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) - - HintSection(title = stringResource(R.string.key_status_indicators_title)) { - Text( - text = stringResource(R.string.key_status_explanation), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(bottom = 8.dp) - ) - KeyStatus(hasKey = true) - Text( - text = stringResource(R.string.key_saved_and_active), - style = MaterialTheme.typography.bodySmall, - modifier = Modifier.padding(start = 20.dp, bottom = 8.dp) - ) - KeyStatus(hasKey = false) - Text( - text = stringResource(R.string.key_missing_or_cleared), - style = MaterialTheme.typography.bodySmall, - modifier = Modifier.padding(start = 20.dp) - ) - } - - HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) - - HintSection(title = stringResource(R.string.troubleshooting_title)) { - Text( - text = stringResource(R.string.troubleshooting_intro), - style = MaterialTheme.typography.bodyMedium - ) - Text( - text = stringResource(R.string.troubleshooting_bullets), - style = MaterialTheme.typography.bodyMedium, - modifier = Modifier.padding(top = 4.dp) - ) - } - } - } +@Composable +fun getApiKeyHint() = Hint ( + titleRes = R.string.hint_how_to_connect_to_an_ai, + elements = listOf( + HintElement.LocalizedMarkdown("api_key_hint") + ) + ) - @Composable - private fun KeyStatus(hasKey: Boolean) { - val (text, color, icon) = if (hasKey) { - Triple( - stringResource(R.string.text_key_active), - MaterialTheme.colorScheme.primary, - AppIcons.Check - ) - } else { - Triple( - stringResource(R.string.text_no_key), - MaterialTheme.colorScheme.error, - AppIcons.Warning - ) - } - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = icon, - contentDescription = text, - tint = color, - modifier = Modifier.size(16.dp) - ) - Spacer(Modifier.width(4.dp)) - Text(text, color = color, style = MaterialTheme.typography.labelLarge) - } - } + +@Composable +fun ApiKeyHint() { + getApiKeyHint().Render() } -@Preview(showBackground = true) +@Preview @Composable fun ApiKeyHintPreview() { MaterialTheme { - ApiKeyHint.Content() + ApiKeyHint() } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHint.kt index 1a71046..7b5451c 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHint.kt @@ -1,121 +1,33 @@ package eu.gaudian.translator.view.hints -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import eu.gaudian.translator.R -import eu.gaudian.translator.view.composable.AppIcons /** - * TODO: Migrate to markdown-based hint format. - * Create a .md file in assets/hints/ and use MarkdownHint composable instead. - * See MarkdownHint.kt for implementation details. + * Migrated Category hint using markdown-based format. + * Content is loaded from localized assets/hints based on device locale. */ -object CategoryHint : LegacyHint() { - override val titleRes: Int = R.string.category_hint_intro - - @Composable - override fun Content() { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - HeaderWithIcon( - title = stringResource(R.string.category_hint_intro) - ) - - HintSection(title = stringResource(R.string.category_hint_intro)) { - // Tag Category Explanation (formerly List) - CategoryHintItem( - icon = { - Icon( - AppIcons.FilterList, - contentDescription = stringResource(R.string.content_desc_tag_category), - modifier = Modifier.size(32.dp), - tint = MaterialTheme.colorScheme.secondary - ) - }, - title = stringResource(R.string.text_list), - description = stringResource(R.string.hint_list_category) - ) - - // Filter Category Explanation - CategoryHintItem( - icon = { - Icon( - AppIcons.FilterCategory, - contentDescription = stringResource(R.string.content_desc_filter_category), - modifier = Modifier.size(32.dp), - tint = MaterialTheme.colorScheme.secondary - ) - }, - title = stringResource(R.string.text_filter), - description = stringResource(R.string.hint_filter_category_description) - ) - } - } - } +@Composable +fun getCategoryHint(): Hint { + return Hint( + titleRes = R.string.category_hint_intro, + elements = listOf( + HintElement.LocalizedMarkdown("category_hint") + ) + ) } @Composable fun CategoryHint() { - CategoryHint.Content() -} - -@Composable -private fun CategoryHintItem( - icon: @Composable () -> Unit, - title: String, - description: String -) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - icon() - Column { - Text(text = title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = description, style = MaterialTheme.typography.bodyMedium) - } - } + getCategoryHint().Render() } @Preview @Composable fun CategoryHintPreview() { - CategoryHint() + MaterialTheme { + CategoryHint() + } } - -@Preview -@Composable -fun CategoryHintItemPreview() { - CategoryHintItem( - icon = { - Icon( - AppIcons.Category, - contentDescription = stringResource(R.string.cd_tag_category), - modifier = Modifier.size(32.dp), - tint = MaterialTheme.colorScheme.secondary - ) - }, - title = stringResource(R.string.text_list), - description = stringResource(R.string.category_hint_item_preview_description) - ) -} \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHintScreen.kt b/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHintScreen.kt index ca0dda1..83fda5a 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHintScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/CategoryHintScreen.kt @@ -1,136 +1,33 @@ package eu.gaudian.translator.view.hints -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import eu.gaudian.translator.R -import eu.gaudian.translator.view.composable.AppIcons /** - * TODO: Migrate to markdown-based hint format. - * Create a .md file in assets/hints/ and use MarkdownHint composable instead. - * See MarkdownHint.kt for implementation details. + * Migrated Category Hint Screen using markdown-based format. + * Content is loaded from localized assets/hints based on device locale. */ -object CategoryHintScreen : LegacyHint() { - override val titleRes: Int = R.string.category_hint_intro - - @Composable - override fun Content() { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - HeaderWithIcon( - title = stringResource(R.string.category_hint_intro) - ) - - HintSection(title = stringResource(R.string.category_list_title)) { - Text( - text = stringResource(R.string.category_list_description), - style = MaterialTheme.typography.bodyMedium - ) - Spacer(modifier = Modifier.height(8.dp)) - // Visual representation for List Category - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - WordItem(text = stringResource(R.string.example_word_apple)) - Icon(AppIcons.Add, contentDescription = stringResource(R.string.action_add), tint = MaterialTheme.colorScheme.secondary) - CategoryBox(icon = AppIcons.FilterList, text = stringResource(R.string.example_category_my_fruit_list)) - } - } - - HorizontalDivider() - - HintSection(title = stringResource(R.string.category_filter_title)) { - Text( - text = stringResource(R.string.category_filter_description), - style = MaterialTheme.typography.bodyMedium - ) - Spacer(modifier = Modifier.height(8.dp)) - // Visual representation for Filter Category - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Row { - WordItem(text = stringResource(R.string.example_word_dog), subtext = stringResource(R.string.stage_1)) - Spacer(modifier = Modifier.padding(horizontal = 4.dp)) - WordItem(text = stringResource(R.string.example_word_cat), subtext = stringResource(R.string.stage_1)) - } - CategoryBox(icon = AppIcons.FilterCategory, text = stringResource(R.string.example_filter_stage_1)) - } - } - } - } +@Composable +fun getCategoryHintScreen(): Hint { + return Hint( + titleRes = R.string.category_hint_intro, + elements = listOf( + HintElement.LocalizedMarkdown("category_hint") + ) + ) } @Composable fun CategoryHintScreen() { - CategoryHintScreen.Content() + getCategoryHintScreen().Render() } -/** - * A simple visual representation of a vocabulary word. - */ -@Composable -private fun WordItem(text: String, subtext: String? = null) { - Box( - modifier = Modifier - .clip(RoundedCornerShape(8.dp)) - .background(MaterialTheme.colorScheme.surfaceContainer) - .padding(horizontal = 12.dp, vertical = 6.dp) - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Text(text = text, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.SemiBold) - subtext?.let { - Text(text = it, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant) - } - } - } -} - -/** - * A simple visual representation of a category. - */ -@Composable -private fun CategoryBox(icon: ImageVector, text: String) { - Row( - modifier = Modifier - .clip(RoundedCornerShape(8.dp)) - .background(MaterialTheme.colorScheme.secondaryContainer) - .padding(horizontal = 12.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Icon(imageVector = icon, contentDescription = text, modifier = Modifier.size(24.dp)) - Text(text = text, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold) - } -} - - -@Preview(showBackground = true) +@Preview @Composable fun CategoryHintScreenPreview() { MaterialTheme { CategoryHintScreen() } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/DictionaryOptionsHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/DictionaryOptionsHint.kt index 92023c4..8a57c13 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/DictionaryOptionsHint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/DictionaryOptionsHint.kt @@ -1,79 +1,25 @@ package eu.gaudian.translator.view.hints -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import eu.gaudian.translator.R -import eu.gaudian.translator.view.composable.AppIcons +/** + * Migrated DictionaryOptionsHint using markdown-based format. + * Content is loaded from localized assets/hints based on device locale. + */ @Composable fun getDictionaryOptionsHint(): Hint { return Hint( titleRes = R.string.label_dictionary_options, elements = listOf( - HintElement.Section( - title = stringResource(R.string.hint_dictionary_desc), - content = listOf( - // VisualStep 1: - HintElement.VisualStep( - step = "1", - title = stringResource(R.string.hint_dict_options_step1_title), - description = stringResource(R.string.hint_dict_options_step1_desc) - ), - // VisualStep 2: - HintElement.VisualStep( - step = "2", - title = stringResource(R.string.hint_dict_options_step2_title), - description = stringResource(R.string.hint_dict_options_step2_desc), - trailing = { - // Custom trailing composable from the original hint - Row( - modifier = Modifier - .clip(RoundedCornerShape(8.dp)) - .background(MaterialTheme.colorScheme.surfaceContainer) - .padding(horizontal = 12.dp, vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - text = stringResource(R.string.eg_synonyms), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Icon( - imageVector = AppIcons.SwitchOn, - contentDescription = stringResource(R.string.example_toggle), - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(32.dp) - ) - } - } - ) - ) - ) + HintElement.LocalizedMarkdown("dictionary_hint") ) ) } -/** - * Preview for the migrated DictionaryOptionsHint. - * It now calls getDictionaryOptionsHint() and then Render(). - */ @Preview @Composable -fun DictionaryOptionsHintPreview() { +fun DictionaryOptionsHint() { getDictionaryOptionsHint().Render() -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/ExampleModernHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/ExampleModernHint.kt index bb6591c..3cdb804 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/ExampleModernHint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/ExampleModernHint.kt @@ -1,162 +1,7 @@ -@file:Suppress("HardCodedStringLiteral") - package eu.gaudian.translator.view.hints -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.material3.FilterChipDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SuggestionChip -import androidx.compose.material3.SuggestionChipDefaults -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import eu.gaudian.translator.R -import eu.gaudian.translator.view.composable.AppCard -import eu.gaudian.translator.view.composable.AppIcons -import eu.gaudian.translator.view.composable.AppOutlinedButton - /** - * Example of a modern hint using the new uniform structure with Hint and HintElements. - * This demonstrates how to migrate from the old Content() based hints to the new element-based system. + * This file is kept for reference only. + * All hints are now migrated to markdown-based format. + * See individual hint files for implementations. */ -val ExampleModernHint = Hint( - titleRes = R.string.hint_example_hint_scan_for_models_hint, - subtitle = "How to find and add AI models to your app", - elements = listOf( - HintElement.Section( - title = "How Scanning Works", - content = listOf( - HintElement.Text("The scan feature searches for available AI models on your device or network."), - HintElement.UIElement { - ReusedScanButtonPreview() - }, - HintElement.BulletList( - listOf( - "Results depend on your API key permissions.", - "Only public models are shown by default.", - "Try again if no models are found." - ) - ) - ) - ), - HintElement.Divider, - HintElement.Section( - title = "Why Some Models Are Missing", - content = listOf( - HintElement.InfoBadge( - icon = AppIcons.Lock, - text = "Restricted access" - ), - HintElement.InfoBadge( - icon = AppIcons.Warning, - text = "Not suitable for this app" - ), - HintElement.InfoBadge( - icon = AppIcons.CheckCircle, - text = "Only text models are supported" - ), - HintElement.Text("Focus on text-based models for best performance."), - HintElement.Card { - PerformanceTierChips() - }, - HintElement.InfoBadge( - icon = AppIcons.Info, - text = "Most tasks work well with smaller models" - ) - ) - ), - HintElement.Divider, - HintElement.Section( - title = "Tips", - content = listOf( - HintElement.BulletList( - listOf( - "Verify your API key is active.", - "Select the correct organization.", - "Type model names manually if needed.", - "Prefer instruct or chat models for text." - ) - ) - ) - ), - HintElement.Divider, - HintElement.Section( - title = "Visual Guide", - content = listOf( - HintElement.VisualStep( - step = "1", - title = "Initiate Scan", - description = "Click the scan button to search for models.", - trailing = { - Icon(AppIcons.Search, contentDescription = null, tint = MaterialTheme.colorScheme.primary) - } - ), - HintElement.VisualStep( - step = "2", - title = "Select Model Type", - description = "Choose between text, chat, or instruct models.", - trailing = { - SuggestionChip( - onClick = {}, - label = { Text("Text Chat") }, - enabled = false, - colors = SuggestionChipDefaults.suggestionChipColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - labelColor = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - } - ), - HintElement.VisualStep( - step = "3", - title = "Add and Validate", - description = "Add the selected model and validate it.", - trailing = { - SmallPrimaryCard(text = "Add & Validate") - } - ) - ) - ), - HintElement.Divider, - HintElement.Section( - title = "Can't Find Your Model?", - content = listOf( - HintElement.Text("You can manually add models by entering their details.") - ) - ) - ) -) - -/** - * Preview composable for the example modern hint. - */ -@Preview -@Composable -fun ExampleModernHintPreview() { - ExampleModernHint.Render() -} - -/** - * Reused composable for scan button preview (from original hint). - */ -@Preview -@Composable -fun ReusedScanButtonPreview() { - AppCard { - AppOutlinedButton(onClick = {}, enabled = true, modifier = Modifier.fillMaxWidth()) { - Icon( - AppIcons.Search, - contentDescription = null, - modifier = Modifier.size(FilterChipDefaults.IconSize) - ) - Spacer(Modifier.width(8.dp)) - Text(text = "Scan for Models") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/Hint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/Hint.kt index 1b0a0ac..58c0edc 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/Hint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/Hint.kt @@ -3,36 +3,20 @@ package eu.gaudian.translator.view.hints import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow 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.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SuggestionChip -import androidx.compose.material3.SuggestionChipDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import eu.gaudian.translator.R -import eu.gaudian.translator.view.composable.AppCard import eu.gaudian.translator.view.composable.AppIcons /** @@ -61,20 +45,7 @@ data class Hint( fun getTitle(): String = stringResource(titleRes) } -/** - * Legacy abstract class for existing hints (to be migrated). - * New hints should use the data class above. - */ -@Deprecated("Use Hint data class instead") -abstract class LegacyHint { - abstract val titleRes: Int - @Composable - abstract fun Content() - - @Composable - open fun getTitle(): String = stringResource(titleRes) -} @Composable fun HeaderWithIcon(title: String, subtitle: String? = null) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -99,226 +70,4 @@ fun HeaderWithIcon(title: String, subtitle: String? = null) { )} } } -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun HeaderWithIconPreview() { - HeaderWithIcon(title = "Sample Title", subtitle = "Sample Subtitle") -} - -@Composable -fun HintSection(title: String, content: @Composable () -> Unit) { - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - content() - } -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun HintSectionPreview() { - AppCard( - modifier = Modifier.fillMaxWidth(), - ) { - HintSection(title = "Sample Section Title") { - Text("Sample section content.") - } - } -} - -@Composable -fun BulletPoints(items: List) { - Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { - items.forEach { line -> - Row(verticalAlignment = Alignment.Top) { - Text("•", modifier = Modifier.width(16.dp), textAlign = TextAlign.Center) - Text(line, style = MaterialTheme.typography.bodyMedium) - } - } - } -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun BulletPointsPreview() { - BulletPoints(items = listOf("Point 1", "Point 2", "Point 3")) -} - -@Composable -fun InfoBadgeRow(icon: ImageVector, text: String) { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon(icon, contentDescription = null, tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(16.dp)) - Spacer(Modifier.width(6.dp)) - Text(text, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant) - } -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun InfoBadgeRowPreview() { - InfoBadgeRow(icon = AppIcons.Info, text = "Sample info badge text") -} - - - -@OptIn(ExperimentalLayoutApi::class) -@Composable -fun PerformanceTierChips() { - FlowRow( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - SuggestionChip( - onClick = {}, - label = { Text(stringResource(R.string.hint_scan_hint_chip_nano)) }, - enabled = false, - colors = SuggestionChipDefaults.suggestionChipColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - labelColor = MaterialTheme.colorScheme.onSecondaryContainer - ) - ) - SuggestionChip( - onClick = {}, - label = { Text(stringResource(R.string.hint_scan_hint_chip_mini)) }, - enabled = false, - colors = SuggestionChipDefaults.suggestionChipColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - labelColor = MaterialTheme.colorScheme.onSecondaryContainer - ) - ) - SuggestionChip( - onClick = {}, - label = { Text(stringResource(R.string.hint_scan_hint_chip_small)) }, - enabled = false, - colors = SuggestionChipDefaults.suggestionChipColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - labelColor = MaterialTheme.colorScheme.onSecondaryContainer - ) - ) - SuggestionChip( - onClick = {}, - label = { Text(stringResource(R.string.hint_scan_hint_chip_medium)) }, - enabled = false, - colors = SuggestionChipDefaults.suggestionChipColors( - containerColor = MaterialTheme.colorScheme.surfaceVariant, - labelColor = MaterialTheme.colorScheme.onSurfaceVariant - ) - ) - SuggestionChip( - onClick = {}, - label = { Text(stringResource(R.string.hint_scan_hint_chip_large_paid)) }, - enabled = false, - colors = SuggestionChipDefaults.suggestionChipColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - labelColor = MaterialTheme.colorScheme.onPrimaryContainer - ) - ) - } -} - -@Preview -@Composable -fun PerformanceTierChipsPreview() { - PerformanceTierChips() -} - - - - - -@Composable -fun VisualStep( - step: String, - title: String, - description: String, - trailing: @Composable (() -> Unit)? = null -) { - AppCard( - modifier = Modifier.fillMaxWidth(), - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - StepBadge(step) - Spacer(Modifier.width(12.dp)) - Column(modifier = Modifier.weight(1f)) { - Text(title, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold) - Text(description, style = MaterialTheme.typography.bodySmall) - } - } - if (trailing != null) { - Spacer(Modifier.width(8.dp)) - Row( - Modifier - .fillMaxWidth() - .padding(12.dp), - verticalAlignment = Alignment.CenterVertically - ){ - trailing() - } - } - } -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun VisualStepPreview() { - VisualStep( - step = "1", - title = "Sample Step Title", - description = "Sample step description.", - trailing = { Icon(AppIcons.Search, contentDescription = null) } - ) -} - -@Composable -private fun StepBadge(step: String) { - Card( - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer), - shape = RoundedCornerShape(8.dp) - ) { - Box(modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp), contentAlignment = Alignment.Center) { - Text(step, color = MaterialTheme.colorScheme.onPrimaryContainer, fontWeight = FontWeight.Bold, fontSize = 12.sp) - } - } -} - -@Preview -@Composable -fun StepBadgePreview() { - StepBadge(step = "1") -} - -@Composable -fun SmallPrimaryCard(text: String) { - Card(colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primary)) { - Box(modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp)) { - Text(text, color = MaterialTheme.colorScheme.onPrimary, fontSize = 12.sp, fontWeight = FontWeight.SemiBold) - } - } -} - -@Composable -fun TextSection(title: String, text: String) { - Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { - Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - Text(text, style = MaterialTheme.typography.bodySmall) - } -} - - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun SmallPrimaryCardPreview() { - SmallPrimaryCard(text = "Sample Text") } \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/HintElement.kt b/app/src/main/java/eu/gaudian/translator/view/hints/HintElement.kt index a22ede9..8108347 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/HintElement.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/HintElement.kt @@ -1,13 +1,19 @@ package eu.gaudian.translator.view.hints -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview -import eu.gaudian.translator.view.composable.AppIcons +import androidx.compose.ui.unit.dp +import dev.jeziellago.compose.markdowntext.MarkdownText + +private const val TAG = "MarkdownHint" /** * Sealed class representing different types of elements that can be used in a Hint. @@ -15,67 +21,20 @@ import eu.gaudian.translator.view.composable.AppIcons */ sealed class HintElement { - /** - * A simple text element. - */ - data class Text(val text: String) : HintElement() - /** - * A header element with an icon, title, and optional subtitle. - */ - data class Header(val title: String, val subtitle: String? = null) : HintElement() - - /** - * A section element that groups content with a title. - */ - data class Section(val title: String, val content: List) : HintElement() - - data class TextSection(val title: String, val text: String) : HintElement() - - /** - * A bullet list element. - */ - data class BulletList(val items: List) : HintElement() - - /** - * A visual step element with step number, title, description, and optional trailing composable. - */ - data class VisualStep( - val step: String, - val title: String, - val description: String, - val trailing: (@Composable () -> Unit)? = null - ) : HintElement() - - /** - * An info badge row with an icon and text. - */ - data class InfoBadge(val icon: ImageVector, val text: String) : HintElement() - - /** - * A divider element. - */ - object Divider : HintElement() - - /** - * A card element with content. - */ - data class Card(val content: @Composable () -> Unit) : HintElement() - - /** - * A chips element for performance tiers or similar. - */ - data class Chips(val chips: List) : HintElement() - - /** - * A small primary card with text. - */ - data class SmallCard(val text: String) : HintElement() - - /** - * A custom UI element with a composable. - */ data class UIElement(val composable: @Composable () -> Unit) : HintElement() + + /** + * A localized markdown file element. + * The file is loaded from assets based on the current device locale. + * Follows Android's locale-qualified resource pattern: + * - assets/hints/ - Default (English) + * - assets/hints-de-rDE/ - German + * - assets/hints-pt-rBR/ - Portuguese (Brazil) + * + * @param fileName The base filename without extension (e.g., "api_key_hint") + */ + data class LocalizedMarkdown(val fileName: String) : HintElement() } /** @@ -84,170 +43,87 @@ sealed class HintElement { @Composable fun RenderHintElement(element: HintElement) { when (element) { - is HintElement.Text -> { - Text( - text = element.text, - style = MaterialTheme.typography.bodyMedium - ) - } - is HintElement.Header -> { - HeaderWithIcon(title = element.title, subtitle = element.subtitle) - } - is HintElement.Section -> { - HintSection(title = element.title) { - element.content.forEach { RenderHintElement(it) } - } - } - is HintElement.BulletList -> { - BulletPoints(items = element.items) - } - is HintElement.VisualStep -> { - VisualStep( - step = element.step, - title = element.title, - description = element.description, - trailing = element.trailing - ) - } - is HintElement.InfoBadge -> { - InfoBadgeRow(icon = element.icon, text = element.text) - } - is HintElement.Divider -> { - HorizontalDivider() - } - is HintElement.Card -> { - element.content() - } - is HintElement.Chips -> { - PerformanceTierChips(chips = element.chips) - } - is HintElement.SmallCard -> { - SmallPrimaryCard(text = element.text) - } + is HintElement.UIElement -> { element.composable() } - is HintElement.TextSection -> { - TextSection(title = element.title, text = element.text) + + is HintElement.LocalizedMarkdown -> { + LocalizedMarkdownContent(fileName = element.fileName) + + // Debug: Show filename when experimental features are enabled + if (eu.gaudian.translator.view.LocalShowExperimentalFeatures.current) { + androidx.compose.foundation.layout.Row( + modifier = Modifier.padding(top = 4.dp) + ) { + androidx.compose.material3.Text( + text = "[DEBUG: ${element.fileName}]", + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.error + ) + } + } } } } /** - * Helper composable for PerformanceTierChips based on list of strings. + * Composable to render localized markdown content. + * Automatically loads the correct locale version based on device settings. + * Falls back to English default if localized version is not available. */ @Composable -fun PerformanceTierChips(chips: List) { - // Assuming chips are resource strings or direct strings - chips.forEach { chip -> - Text(text = chip, style = MaterialTheme.typography.bodySmall) // Simplified for template +fun LocalizedMarkdownContent( + fileName: String, + modifier: Modifier = Modifier +) { + val context = LocalContext.current + val content = remember(fileName) { + val locale = MarkdownHintLoader.getCurrentLocale(context) + val suffix = MarkdownHintLoader.getLocaleSuffix(locale) + + // Try localized version (folder has suffix, filename doesn't) + val localizedPath = "hints$suffix/$fileName.md" + + android.util.Log.d(TAG, "Loading hint: $fileName") + android.util.Log.d(TAG, "Device locale: ${locale.language}_${locale.country}") + android.util.Log.d(TAG, "Localized path: $localizedPath") + + val localized = MarkdownHintLoader.loadFromAssets(context, localizedPath) + if (localized != null) { + android.util.Log.d(TAG, "Found localized version at: $localizedPath") + localized + } else { + // Fall back to English default in hints folder + val defaultPath = "hints/$fileName.md" + android.util.Log.d(TAG, "Localized not found, trying default: $defaultPath") + val default = MarkdownHintLoader.loadFromAssets(context, defaultPath) + if (default != null) { + android.util.Log.d(TAG, "Found default version at: $defaultPath") + } else { + android.util.Log.e(TAG, "No hint found for: $fileName (tried: $localizedPath, $defaultPath)") + } + default + } + } + + Column( + modifier = modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + ) { + if (content != null) { + MarkdownText( + markdown = content, + style = MaterialTheme.typography.bodyMedium.copy( + color = MaterialTheme.colorScheme.onSurface + ) + ) + } + // If content is null, nothing is rendered (empty hint) } } -// Preview Composables for each HintElement type - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun TextElementPreview() { - RenderHintElement(HintElement.Text("This is a sample text element.")) -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun HeaderElementPreview() { - RenderHintElement(HintElement.Header("Sample Header", "Optional subtitle")) -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun SectionElementPreview() { - RenderHintElement( - HintElement.Section( - title = "Sample Section", - content = listOf( - HintElement.Text("Content 1"), - HintElement.Text("Content 2") - ) - ) - ) -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun BulletListElementPreview() { - RenderHintElement( - HintElement.BulletList( - listOf("Item 1", "Item 2", "Item 3") - ) - ) -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun VisualStepElementPreview() { - RenderHintElement( - HintElement.VisualStep( - step = "1", - title = "Sample Step", - description = "This is a description of the step.", - trailing = { - Icon(AppIcons.Info, contentDescription = null) - } - ) - ) -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun InfoBadgeElementPreview() { - RenderHintElement( - HintElement.InfoBadge( - icon = AppIcons.Info, - text = "Sample info badge" - ) - ) -} - -@Preview -@Composable -fun DividerElementPreview() { - RenderHintElement(HintElement.Divider) -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun CardElementPreview() { - RenderHintElement( - HintElement.Card { - Text("Content inside card") - } - ) -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun ChipsElementPreview() { - RenderHintElement( - HintElement.Chips( - listOf("Chip 1", "Chip 2", "Chip 3") - ) - ) -} - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -fun SmallCardElementPreview() { - RenderHintElement(HintElement.SmallCard("Sample Card")) -} @Suppress("HardCodedStringLiteral") @Preview @@ -258,4 +134,13 @@ fun UIElementPreview() { Text("Custom UI Element") } ) -} \ No newline at end of file +} + +@Suppress("HardCodedStringLiteral") +@Preview +@Composable +fun LocalizedMarkdownElementPreview() { + RenderHintElement( + HintElement.LocalizedMarkdown("example_hint") + ) +} diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/HintScreens.kt b/app/src/main/java/eu/gaudian/translator/view/hints/HintScreens.kt index b6444df..a210ae0 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/HintScreens.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/HintScreens.kt @@ -1,7 +1,9 @@ package eu.gaudian.translator.view.hints import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource import androidx.navigation.NavController +import eu.gaudian.translator.R /** * Wrapper for Category Hint Screen @@ -10,9 +12,9 @@ import androidx.navigation.NavController fun CategoryHintScreenWrapper(navController: NavController) { HintScreen( navController = navController, - title = CategoryHint.getTitle() + title = stringResource(R.string.category_hint_intro) ) { - CategoryHint.Content() + CategoryHint() } } @@ -23,9 +25,9 @@ fun CategoryHintScreenWrapper(navController: NavController) { fun DictionaryHintScreen(navController: NavController) { HintScreen( navController = navController, - title = getDictionaryOptionsHint().getTitle() + title = stringResource(R.string.label_dictionary_options) ) { - getDictionaryOptionsHint() + getDictionaryOptionsHint().Render() } } @@ -36,7 +38,7 @@ fun DictionaryHintScreen(navController: NavController) { fun ImportHintScreen(navController: NavController) { HintScreen( navController = navController, - title = getImportVocabularyHint().getTitle() + title = stringResource(R.string.hint_how_to_generate_vocabulary_with_ai) ) { getImportVocabularyHint() } @@ -49,9 +51,9 @@ fun ImportHintScreen(navController: NavController) { fun SortingHintScreen(navController: NavController) { HintScreen( navController = navController, - title = SortingScreenHint.getTitle() + title = stringResource(R.string.sorting_hint_title) ) { - SortingScreenHint.Content() + SortingScreenHint() } } @@ -62,9 +64,9 @@ fun SortingHintScreen(navController: NavController) { fun StagesHintScreen(navController: NavController) { HintScreen( navController = navController, - title = LearningStagesHint.getTitle() + title = stringResource(R.string.learning_stages_title) ) { - LearningStagesHint.Content() + LearningStagesHint() } } @@ -75,9 +77,9 @@ fun StagesHintScreen(navController: NavController) { fun TranslationHintScreen(navController: NavController) { HintScreen( navController = navController, - title = getTranslationScreenHint().getTitle() + title = stringResource(R.string.hint_translate_how_it_works) ) { - getTranslationScreenHint() + getTranslationScreenHint().Render() } } @@ -88,7 +90,7 @@ fun TranslationHintScreen(navController: NavController) { fun ScanHintScreen(navController: NavController) { HintScreen( navController = navController, - title = getAddModelScanHint().getTitle() + title = stringResource(R.string.hint_scan_hint_title) ) { AddModelScanHint() } @@ -101,9 +103,9 @@ fun ScanHintScreen(navController: NavController) { fun ApiHintScreen(navController: NavController) { HintScreen( navController = navController, - title = ApiKeyHint.getTitle() + title = stringResource(R.string.hint_how_to_connect_to_an_ai) ) { - ApiKeyHint.Content() + ApiKeyHint() } } @@ -114,8 +116,8 @@ fun ApiHintScreen(navController: NavController) { fun VocabularyProgressHintScreen(navController: NavController) { HintScreen( navController = navController, - title = VocabularyProgressHint.getTitle() + title = stringResource(R.string.hint_vocabulary_progress_hint_title) ) { - VocabularyProgressHint.Content() + VocabularyProgressHint() } } diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/HintsOverviewScreen.kt b/app/src/main/java/eu/gaudian/translator/view/hints/HintsOverviewScreen.kt index 22b2034..839c73b 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/HintsOverviewScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/HintsOverviewScreen.kt @@ -41,38 +41,37 @@ fun HintsOverviewScreen( ) { val showExperimental = LocalShowExperimentalFeatures.current + // Get hints using the new function-based approach val importHint = getImportVocabularyHint() val addModelScanHint = getAddModelScanHint() val dictionaryOptionsHint = getDictionaryOptionsHint() val translationScreenHint = getTranslationScreenHint() + val categoryHint = getCategoryHint() + val learningStagesHint = getLearningStagesHint() + val sortingScreenHint = getSortingScreenHint() + val vocabularyProgressHint = getVocabularyProgressHint() + val apiKeyHint = getApiKeyHint() - - - - val hintGroups = remember(showExperimental, importHint) { + val hintGroups = remember(showExperimental, importHint, addModelScanHint, dictionaryOptionsHint, translationScreenHint) { val allGroups = listOf( R.string.hint_hints_header_basics to listOf( - HintItem(CategoryHint.titleRes, AppIcons.Category, SettingsRoutes.HINTS_CATEGORIES), - HintItem(LearningStagesHint.titleRes, AppIcons.Stages, SettingsRoutes.HINTS_STAGES), + HintItem(categoryHint.titleRes, AppIcons.Category, SettingsRoutes.HINTS_CATEGORIES), + HintItem(learningStagesHint.titleRes, AppIcons.Stages, SettingsRoutes.HINTS_STAGES), HintItem(translationScreenHint.titleRes, AppIcons.Translate, SettingsRoutes.HINTS_TRANSLATION) ), R.string.hint_hints_header_vocabulary to listOf( HintItem(importHint.titleRes, AppIcons.Vocabulary, SettingsRoutes.HINTS_IMPORT), - HintItem(SortingScreenHint.titleRes, AppIcons.Sort, SettingsRoutes.HINTS_SORTING), + HintItem(sortingScreenHint.titleRes, AppIcons.Sort, SettingsRoutes.HINTS_SORTING), HintItem(dictionaryOptionsHint.titleRes, AppIcons.Dictionary, SettingsRoutes.HINTS_DICTIONARY), - HintItem(VocabularyProgressHint.titleRes, AppIcons.Stages, SettingsRoutes.HINTS_VOCABULARY_PROGRESS) + HintItem(vocabularyProgressHint.titleRes, AppIcons.Stages, SettingsRoutes.HINTS_VOCABULARY_PROGRESS) ), R.string.hint_hints_header_advanced to listOf( HintItem(addModelScanHint.titleRes, AppIcons.AI, SettingsRoutes.HINTS_SCAN), - HintItem(ApiKeyHint.titleRes, AppIcons.ApiKey, SettingsRoutes.HINTS_API) + HintItem(apiKeyHint.titleRes, AppIcons.ApiKey, SettingsRoutes.HINTS_API) ) ) - if (showExperimental) { - allGroups - } else { - allGroups - } + allGroups } AppOutlinedCard { @@ -185,4 +184,4 @@ fun HintListItemPreview() { icon = AppIcons.Category, onClick = {} ) -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/ImportVocabularyHints.kt b/app/src/main/java/eu/gaudian/translator/view/hints/ImportVocabularyHints.kt index 2efe788..c1c08a8 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/ImportVocabularyHints.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/ImportVocabularyHints.kt @@ -1,173 +1,45 @@ package eu.gaudian.translator.view.hints -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.width -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource 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 /** - * Provides the migrated Hint for ImportVocabulary. - * This function is @Composable to access stringResource. + * Migrated ImportVocabularyHint using markdown-based format. + * Content is loaded from localized assets/hints based on device locale. */ @Composable fun getImportVocabularyHint(): Hint { return Hint( titleRes = R.string.hint_how_to_generate_vocabulary_with_ai, - elements = listOf( - HintElement.Section( - title = stringResource(R.string.import_ai_intro), - content = listOf( - // VisualStep 1: Search Term - HintElement.VisualStep( - step = "1", - title = stringResource(R.string.import_step1_title), - description = stringResource(R.string.text_hint_you_can_search), - trailing = { - // The AppTextField is wrapped in the VisualStep's trailing composable - AppOutlinedTextField( - value = stringResource(R.string.search_term_placeholder), - onValueChange = {}, - label = { Text(stringResource(R.string.text_search_term)) }, - modifier = Modifier.fillMaxWidth(), - enabled = false - ) - } - ), - // VisualStep 2: Select Languages - HintElement.VisualStep( - step = "2", - title = stringResource(R.string.import_step2_title), - description = stringResource(R.string.import_step2_desc) - ), - // VisualStep 3: Select Amount - HintElement.VisualStep( - step = "3", - title = stringResource(R.string.import_step3_title), - description = stringResource(R.string.import_step3_desc), - trailing = { - // The Column with Slider and Text is wrapped in the trailing composable - Column { - AppSlider( - value = 10f, - onValueChange = {}, - valueRange = 1f..25f, - steps = 24, - modifier = Modifier.fillMaxWidth(), - enabled = false - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(R.string.import_after_generating), - style = MaterialTheme.typography.bodyMedium - ) - } - } - ) - ) - ) + elements = listOf( + HintElement.LocalizedMarkdown("import_hint") ) ) } -/** - * Preview for the migrated ImportVocabularyHint. - * It now calls getImportVocabularyHint() and then Render(). - */ @Preview @Composable -fun ImportVocabularyHintPreview() { +fun ImportVocabularyHint() { getImportVocabularyHint().Render() } /** - * Provides the migrated Hint for VocabularyReview. - * This function is @Composable to access stringResource. + * Migrated VocabularyReviewHint using markdown-based format. + * Content is loaded from localized assets/hints based on device locale. */ - @Composable fun getVocabularyReviewHint(): Hint { return Hint( titleRes = R.string.review_intro, elements = listOf( - // Section 1: Select Items - HintElement.Section( - title = stringResource(R.string.review_select_items_title), - content = listOf( - HintElement.Text(stringResource(R.string.review_select_items_desc)), - // Custom Row with Checkbox is wrapped in a UIElement - HintElement.UIElement { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - AppCheckbox(checked = true, onCheckedChange = {}, enabled = false) - Spacer(modifier = Modifier.width(8.dp)) - Column { - Text(text = stringResource(R.string.example_word_der_apfel), style = MaterialTheme.typography.bodyLarge) - Text(text = stringResource(R.string.example_word_the_apple), style = MaterialTheme.typography.bodyMedium) - } - } - } - ) - ), - // Divider - HintElement.Divider, - // Section 2: Duplicate Handling - HintElement.Section( - title = stringResource(R.string.duplicate_handling_title), - content = listOf( - HintElement.Text(stringResource(R.string.duplicate_handling_desc)), - // Custom Row with Checkbox and Error Text is wrapped in a UIElement - HintElement.UIElement { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - AppCheckbox(checked = false, onCheckedChange = {}, enabled = false) - Spacer(modifier = Modifier.width(8.dp)) - Column { - Text(text = stringResource(R.string.example_word_der_hund), style = MaterialTheme.typography.bodyLarge) - Text(text = stringResource(R.string.example_word_the_dog), style = MaterialTheme.typography.bodyMedium) - } - Spacer(modifier = Modifier.weight(1f)) - Text(stringResource(R.string.duplicate), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.error) - } - } - ) - ), - // Divider - HintElement.Divider, - // Section 3: Add to List - HintElement.Section( - title = stringResource(R.string.add_to_list_optional), - content = listOf( - HintElement.Text(stringResource(R.string.add_to_list_optional_desc)) - ) - ) + HintElement.LocalizedMarkdown("review_hint") ) ) } -/** - * Preview for the migrated VocabularyReviewHint. - * It now calls getVocabularyReviewHint() and then Render(). - */ -@Preview + @Composable -fun VocabularyReviewHintPreview() { - getVocabularyReviewHint().Render() -} \ No newline at end of file +fun VocabularyReviewHint() { + getVocabularyReviewHint() +} diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/LearningStagesHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/LearningStagesHint.kt index efcc3c7..97d1eb2 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/LearningStagesHint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/LearningStagesHint.kt @@ -1,205 +1,33 @@ package eu.gaudian.translator.view.hints -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.PathEffect -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import eu.gaudian.translator.R -import eu.gaudian.translator.view.composable.AppIcons - -private data class LearningStage( - val icon: ImageVector, - val name: String, - val interval: String? = null -) /** - * TODO: Migrate to markdown-based hint format. - * Create a .md file in assets/hints/ and use MarkdownHint composable instead. - * See MarkdownHint.kt for implementation details. + * Migrated Learning Stages hint using markdown-based format. + * Content is loaded from localized assets/hints based on device locale. */ -object LearningStagesHint : LegacyHint() { - override val titleRes: Int = R.string.learning_stages_title - - @Composable - override fun Content() { - val stages = listOf( - LearningStage(AppIcons.StageNew, stringResource(R.string.stage_new)), - LearningStage(AppIcons.Stage1, stringResource(R.string.stage_1), stringResource(R.string.interval_1_day)), - LearningStage(AppIcons.Stage2, stringResource(R.string.stage_2), stringResource(R.string.interval_3_days)), - LearningStage(AppIcons.Stage3, stringResource(R.string.stage_3), stringResource(R.string.interval_1_week)), - LearningStage(AppIcons.Stage4, stringResource(R.string.stage_4), stringResource(R.string.interval_2_weeks)), - LearningStage(AppIcons.Stage5, stringResource(R.string.stage_5), stringResource(R.string.interval_1_month)), - LearningStage(AppIcons.StageLearned, stringResource(R.string.stage_learned)) +@Composable +fun getLearningStagesHint(): Hint { + return Hint( + titleRes = R.string.learning_stages_title, + elements = listOf( + HintElement.LocalizedMarkdown("learning_stages_hint") ) - - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - HeaderWithIcon( - title = stringResource(R.string.learning_stages_title) - ) - - HintSection(title = stringResource(R.string.learning_stages_title)) { - Column( - verticalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - // Group stages into chunks of 2 to create rows. - stages.chunked(2).forEach { rowItems -> - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center - ) { - rowItems.forEach { stage -> - StageNode(stage) - // If the stage has an interval, show the connector arrow - stage.interval?.let { interval -> - StageConnector(interval) - } - } - } - } - } - } - - HintSection(title = stringResource(R.string.hint_how_it_works)) { - RuleExplanation( - icon = AppIcons.Check, - title = stringResource(R.string.hint_answer_correctly), - description = stringResource(R.string.hint_the_word_moves), - tint = MaterialTheme.colorScheme.primary - ) - Spacer(modifier = Modifier.height(8.dp)) - RuleExplanation( - icon = AppIcons.Error, - title = stringResource(R.string.hint_answer_incorrectly), - description = stringResource(R.string.hint_the_word_moves_back_another_stage_this_helps_you_focus_on_), - tint = MaterialTheme.colorScheme.error - ) - Spacer(modifier = Modifier.height(8.dp)) - RuleExplanation( - icon = AppIcons.Info, - title = stringResource(R.string.hint_customizable), - description = stringResource(R.string.hint_you_can_costumize_all_intervals_and_rules_in_the_settings), - tint = MaterialTheme.colorScheme.primary - ) - } - } - } + ) } @Composable fun LearningStagesHint() { - LearningStagesHint.Content() + getLearningStagesHint().Render() } -/** - * A composable that displays a single stage icon and its name. - */ -@Composable -private fun StageNode(stage: LearningStage) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(4.dp) - ) { - Icon( - imageVector = stage.icon, - contentDescription = stage.name, - modifier = Modifier.size(32.dp), - tint = MaterialTheme.colorScheme.primary - ) - Text( - text = stage.name, - style = MaterialTheme.typography.labelMedium - ) - } -} - -/** - * A composable that draws a dashed arrow and displays the time interval between stages. - */ -@Composable -private fun StageConnector(interval: String) { - val arrowColor = MaterialTheme.colorScheme.onSurfaceVariant - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.padding(horizontal = 8.dp) - ) { - Text( - text = interval, - style = MaterialTheme.typography.bodySmall, - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.secondary - ) - Spacer(modifier = Modifier.height(4.dp)) - Canvas(modifier = Modifier.size(width = 50.dp, height = 10.dp)) { - val startY = center.y - val endX = size.width - // Draw the dashed line - drawLine( - color = arrowColor, - start = Offset(0f, startY), - end = Offset(endX - 5.dp.toPx(), startY), - strokeWidth = 1.5.dp.toPx(), - pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f) - ) - // Draw the arrowhead - drawLine(arrowColor, Offset(endX - 5.dp.toPx(), startY - 4.dp.toPx()), Offset(endX, startY), strokeWidth = 1.5.dp.toPx()) - drawLine(arrowColor, Offset(endX - 5.dp.toPx(), startY + 4.dp.toPx()), Offset(endX, startY), strokeWidth = 1.5.dp.toPx()) - } - } -} - -/** - * A helper composable to explain the success/failure rules. - */ -@Composable -private fun RuleExplanation(icon: ImageVector, title: String, description: String, tint: androidx.compose.ui.graphics.Color) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - Icon( - imageVector = icon, - contentDescription = title, - modifier = Modifier.size(28.dp), - tint = tint - ) - Column { - Text(text = title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - Text(text = description, style = MaterialTheme.typography.bodyMedium) - } - } -} - -@Preview(showBackground = true) +@Preview @Composable fun LearningStagesHintPreview() { MaterialTheme { - Box(modifier = Modifier.padding(8.dp)) { - LearningStagesHint() - } + LearningStagesHint() } -} \ No newline at end of file +} diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/MarkdownHintLoader.kt b/app/src/main/java/eu/gaudian/translator/view/hints/MarkdownHintLoader.kt new file mode 100644 index 0000000..fb84606 --- /dev/null +++ b/app/src/main/java/eu/gaudian/translator/view/hints/MarkdownHintLoader.kt @@ -0,0 +1,225 @@ +package eu.gaudian.translator.view.hints + +import android.content.Context +import android.os.Build +import java.util.Locale + +/** + * Internationalization system for markdown hints. + * + * This follows Android's locale-qualified resource pattern: + * - assets/hints/ - Default (English) + * - assets/hints-de-rDE/ - German + * - assets/hints-pt-rBR/ - Portuguese (Brazil) + * - etc. + * + * Usage: + * val content = MarkdownHintLoader.loadHint(context, "api_key_hint") + * MarkdownHintLoader.getHintFileName("api_key_hint") // Returns localized filename + */ +object MarkdownHintLoader { + + /** + * Load markdown content with automatic locale detection. + * + * @param context The context for accessing assets + * @param hintFileName The base filename without locale suffix (e.g., "api_key_hint") + * @return The markdown content as a string, or null if not found + */ + fun loadHint(context: Context, hintFileName: String): String? { + val locale = getCurrentLocale(context) + val suffix = getLocaleSuffix(locale) + + // Try localized version (folder has suffix, filename doesn't) + val localizedPath = "hints$suffix/$hintFileName.md" + val localizedContent = loadFromAssets(context, localizedPath) + if (localizedContent != null) { + return localizedContent + } + + // Try with just language code (e.g., hints-pt/ instead of hints-pt-rBR/) + val languageSuffix = if (locale.country.isNotEmpty()) "-${locale.language}" else "" + val languageOnlyPath = "hints$languageSuffix/$hintFileName.md" + val languageContent = loadFromAssets(context, languageOnlyPath) + if (languageContent != null) { + return languageContent + } + + // Fall back to default (English) in hints folder + val defaultPath = "hints/$hintFileName.md" + val defaultContent = loadFromAssets(context, defaultPath) + if (defaultContent != null) { + return defaultContent + } + + return null + } + + /** + * Get the localized file path for a hint. + * + * @param hintFileName The base filename (e.g., "api_key_hint") + * @return The full path including locale folder (e.g., "hints-de-rDE/api_key_hint.md") + */ + fun getHintPath(hintFileName: String): String { + val locale = Locale.getDefault() + val localeSuffix = getLocaleSuffix(locale) + return "hints$localeSuffix/$hintFileName.md" + } + + /** + * Get the localized file name for a hint. + * + * @param hintFileName The base filename (e.g., "api_key_hint") + * @param locale The target locale + * @return The file name with locale suffix (e.g., "api_key_hint-de-rDE.md") + */ + fun getLocalizedFileName(hintFileName: String, locale: Locale): String { + val localeSuffix = getLocaleSuffix(locale) + return "$hintFileName$localeSuffix.md" + } + + /** + * Get the file name with language-only suffix. + * + * @param hintFileName The base filename + * @param locale The target locale + * @return The file name with language suffix (e.g., "api_key_hint-de.md") + */ + private fun getLanguageOnlyFileName(hintFileName: String, locale: Locale): String { + val languageCode = locale.language + return "$hintFileName-$languageCode.md" + } + + /** + * Get the current device locale. + */ + @Suppress("DEPRECATION") + fun getCurrentLocale(context: Context): Locale { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + context.resources.configuration.locales[0] + } else { + context.resources.configuration.locale + } + } + + /** + * Get all supported locales for hints. + */ + fun getSupportedLocales(): List { + return listOf( + Locale.ENGLISH, // Default + Locale.GERMAN, // de-rDE + Locale("pt", "BR"), // pt-rBR + Locale.FRENCH, // fr-rFR + Locale("es", "ES"), // es-rES + Locale.ITALIAN, // it-rIT + Locale("nl", "NL"), // nl-rNL + Locale("hr", "HR") // hr-rHR + ) + } + + /** + * Check if a localized version exists for the given locale. + */ + fun localizedVersionExists(context: Context, hintFileName: String, locale: Locale): Boolean { + val localizedFileName = getLocalizedFileName(hintFileName, locale) + val suffix = getLocaleSuffix(locale) + return assetExists(context, "hints$suffix/$localizedFileName") + } + + /** + * Load content from assets. + */ + fun loadFromAssets(context: Context, fileName: String): String? { + return try { + context.assets.open(fileName).bufferedReader().use { it.readText() } + } catch (e: Exception) { + null + } + } + + /** + * Check if an asset file exists. + */ + private fun assetExists(context: Context, path: String): Boolean { + return try { + context.assets.open(path).close() + true + } catch (e: Exception) { + false + } + } + + /** + * Get the locale suffix string. + */ + fun getLocaleSuffix(locale: Locale): String { + val language = locale.language + val country = locale.country + + return buildString { + if (language.isNotEmpty()) { + append("-") + append(language.lowercase()) + } + if (country.isNotEmpty()) { + append("-r") + append(country.uppercase()) + } + } + } +} + +/** + * Extension function to get localized hint content. + */ +fun Context.loadLocalizedHint(hintFileName: String): String? { + return MarkdownHintLoader.loadHint(this, hintFileName) +} + +/** + * Data class for localized hint information. + */ +data class LocalizedHint( + val fileName: String, + val locale: Locale, + val isDefault: Boolean = false +) + +/** + * Hint localization manager that tracks available translations. + */ +object HintLocalizationManager { + + /** + * Get all available translations for a hint. + */ + fun getAvailableTranslations(context: Context, baseFileName: String): List { + val available = mutableListOf() + val defaultLocale = MarkdownHintLoader.getCurrentLocale(context) + + // Check each supported locale + MarkdownHintLoader.getSupportedLocales().forEach { locale -> + val fileName = MarkdownHintLoader.getLocalizedFileName(baseFileName, locale) + val path = "hints${MarkdownHintLoader.getLocaleSuffix(locale)}/$fileName" + + if (MarkdownHintLoader.loadFromAssets(context, path) != null) { + available.add(LocalizedHint( + fileName = fileName, + locale = locale, + isDefault = locale.language == "en" && locale.country.isEmpty() + )) + } + } + + return available.sortedBy { it.locale.language } + } + + /** + * Get the best available translation for current locale. + */ + fun getBestTranslation(context: Context, baseFileName: String): String? { + return MarkdownHintLoader.loadHint(context, baseFileName) + } +} diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/SortingScreenHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/SortingScreenHint.kt index e083f85..7480562 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/SortingScreenHint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/SortingScreenHint.kt @@ -1,244 +1,33 @@ package eu.gaudian.translator.view.hints -import androidx.compose.foundation.BorderStroke -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.FilterChipDefaults -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.SuggestionChip -import androidx.compose.material3.SuggestionChipDefaults -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -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.AppOutlinedTextField /** - * TODO: Migrate to markdown-based hint format. - * Create a .md file in assets/hints/ and use MarkdownHint composable instead. - * See MarkdownHint.kt for implementation details. - */ -object SortingScreenHint : LegacyHint() { - override val titleRes: Int = R.string.sorting_hint_title - - @Composable - override fun getTitle(): String = stringResource(R.string.sorting_hint_title) - - @Composable - override fun Content() { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - HeaderWithIcon( - title = stringResource(R.string.sorting_hint_title) - ) - - HintSection(title = stringResource(R.string.sorting_hint_intro_text)) { - @Suppress("HardCodedStringLiteral") - AppOutlinedTextField( - value = "der Hund", - onValueChange = {}, - label = { Text(stringResource(R.string.label_word)) }, - modifier = Modifier.fillMaxWidth(), - enabled = false - ) - @Suppress("HardCodedStringLiteral") - AppOutlinedTextField( - value = "the dog", - onValueChange = {}, - label = { Text(stringResource(R.string.label_translation)) }, - modifier = Modifier.fillMaxWidth(), - enabled = false - ) - } - - HorizontalDivider() - - HintSection(title = stringResource(R.string.sorting_hint_helper_text)) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceEvenly - ) { - SuggestionChip( - onClick = {}, - label = { Text(stringResource(R.string.sorting_hint_chip_duplicate)) }, - icon = { - Icon( - imageVector = AppIcons.Warning, - contentDescription = stringResource(R.string.label_warning), - modifier = Modifier.size(FilterChipDefaults.IconSize) - ) - }, - colors = SuggestionChipDefaults.suggestionChipColors( - containerColor = MaterialTheme.colorScheme.errorContainer, - labelColor = MaterialTheme.colorScheme.onErrorContainer, - iconContentColor = MaterialTheme.colorScheme.onErrorContainer - ), - border = BorderStroke(1.dp, MaterialTheme.colorScheme.error), - enabled = false - ) - - SuggestionChip( - onClick = {}, - label = { Text(stringResource(R.string.label_remove_articles)) }, - icon = { - Icon( - imageVector = AppIcons.Clean, - contentDescription = stringResource(R.string.label_remove_articles), - modifier = Modifier.size(FilterChipDefaults.IconSize) - ) - }, - border = BorderStroke(1.dp, MaterialTheme.colorScheme.onSurface), - enabled = false - ) - } - } - - HorizontalDivider() - - HintSection(title = stringResource(R.string.sorting_hint_decide_next_action)) { - LabeledSegmentedIconButtons( - onDeleteClick = {}, - onLearnedClick = {}, - onDoneClick = {} - ) - } - } - } -} - -@Preview -@Composable -private fun SortingScreenHintContentPreview() { - SortingScreenHint.Content() -} - -/** - * A non-functional, recycled composable for visual demonstration in the hint. + * Migrated Sorting Screen hint using markdown-based format. + * Content is loaded from localized assets/hints based on device locale. */ @Composable -private fun LabeledSegmentedIconButtons( - onDeleteClick: () -> Unit, - onLearnedClick: () -> Unit, - onDoneClick: () -> Unit -) { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - val buttonHeight = 48.dp - val cornerRadius = 24.dp - val secondaryButtonColor = MaterialTheme.colorScheme.surfaceVariant - - Row( - modifier = Modifier - .fillMaxWidth() - .height(buttonHeight) - .clip(RoundedCornerShape(cornerRadius)), - verticalAlignment = Alignment.CenterVertically - ) { - AppButton( - onClick = onDeleteClick, - modifier = Modifier - .weight(1f) - .fillMaxHeight(), - shape = RoundedCornerShape(0.dp), - colors = ButtonDefaults.buttonColors(containerColor = secondaryButtonColor), - enabled = false - ) { - Icon(AppIcons.Delete, contentDescription = stringResource(R.string.label_delete), tint = MaterialTheme.colorScheme.onSurfaceVariant) - } - - VerticalDivider() - - AppButton( - onClick = onLearnedClick, - modifier = Modifier - .weight(1f) - .fillMaxHeight(), - shape = RoundedCornerShape(0.dp), - colors = ButtonDefaults.buttonColors(containerColor = secondaryButtonColor), - enabled = false - ) { - Icon(AppIcons.StageLearned, contentDescription = stringResource(R.string.label_learned), tint = MaterialTheme.colorScheme.onSurfaceVariant) - } - - AppButton( - onClick = onDoneClick, - modifier = Modifier - .weight(2f) - .fillMaxHeight(), - shape = RoundedCornerShape(0.dp), - colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary), - enabled = false - ) { - Icon(AppIcons.Stage1, contentDescription = stringResource(R.string.label_done), tint = MaterialTheme.colorScheme.onSurfaceVariant) - } - } - - Spacer(Modifier.height(4.dp)) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp) - ) { - Text(stringResource(R.string.label_delete), Modifier.weight(1f), textAlign = TextAlign.Center, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) - Text(stringResource(R.string.label_learned), Modifier.weight(1f), textAlign = TextAlign.Center, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant) - Text(stringResource(R.string.label_move_first_stage), Modifier.weight(2f), textAlign = TextAlign.Center, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurface) - } - } -} - -@Preview -@Composable -private fun LabeledSegmentedIconButtonsPreview() { - LabeledSegmentedIconButtons(onDeleteClick = {}, onLearnedClick = {}, onDoneClick = {}) -} - - -@Composable -private fun VerticalDivider( - modifier: Modifier = Modifier, - thickness: Dp = 1.dp, - color: Color = MaterialTheme.colorScheme.background.copy(alpha = 0.5f) -) { - Box( - modifier - .fillMaxHeight() - .width(thickness) - .background(color = color) +fun getSortingScreenHint(): Hint { + return Hint( + titleRes = R.string.sorting_hint_title, + elements = listOf( + HintElement.LocalizedMarkdown("sorting_hint") + ) ) } +@Composable +fun SortingScreenHint() { + getSortingScreenHint() +} + @Preview @Composable -private fun VerticalDividerPreview() { - VerticalDivider() -} \ No newline at end of file +fun SortingScreenHintPreview() { + MaterialTheme { + SortingScreenHint() + } +} diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/TranslationScreenHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/TranslationScreenHint.kt index 8ff65c5..b696191 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/TranslationScreenHint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/TranslationScreenHint.kt @@ -1,62 +1,21 @@ package eu.gaudian.translator.view.hints -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import eu.gaudian.translator.R -import eu.gaudian.translator.view.composable.AppIcons +/** + * Migrated TranslationScreenHint using markdown-based format. + * Content is loaded from localized assets/hints based on device locale. + */ @Composable fun getTranslationScreenHint() = Hint( titleRes = R.string.hint_translate_how_it_works, elements = listOf( - HintElement.TextSection( - title = stringResource(R.string.hint_translate_alternative_translations_title), - text = stringResource(R.string.hint_translate_alternative_translations_desc) - ), - HintElement.TextSection( - title = stringResource(R.string.hint_translate_custom_prompts_title), - text = stringResource(R.string.hint_translate_custom_prompts_desc), - ), - - HintElement.TextSection( - title = stringResource(R.string.hint_translate_multiple_services_title), - text = stringResource(R.string.hint_translate_multiple_services_desc) - ), - HintElement.TextSection( - title = stringResource(R.string.hint_translate_history_title), - text = stringResource(R.string.hint_translate_history_desc) - ), - HintElement.TextSection( - title = stringResource(R.string.hint_translate_tts_title), - text = stringResource(R.string.hint_translate_tts_desc) - ), - HintElement.TextSection( - title = stringResource(R.string.hint_translate_quick_actions_title), - text = stringResource(R.string.hint_translate_quick_actions_desc) - ), - HintElement.TextSection( - title = stringResource(R.string.hint_translate_model_selection_title), - text = stringResource(R.string.hint_translate_model_selection_desc)) - ) + HintElement.LocalizedMarkdown("translation_hint") + ) ) - @Composable fun TranslationScreenHint() { getTranslationScreenHint().Render() @@ -67,38 +26,3 @@ fun TranslationScreenHint() { fun TranslationScreenHintPreview() { getTranslationScreenHint().Render() } - -@Composable -private fun TranslationHintItem( - icon: ImageVector, - title: String, - description: String -) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - Icon( - imageVector = icon, - contentDescription = title, - modifier = Modifier.size(32.dp), - tint = MaterialTheme.colorScheme.secondary - ) - Column { - Text(text = title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = description, style = MaterialTheme.typography.bodyMedium) - } - } -} - -@Preview -@Composable -private fun TranslationHintItemPreview() { - TranslationHintItem( - icon = AppIcons.Info, - title = stringResource(R.string.hint_translation_context_aware_title), - description = stringResource(R.string.hint_translation_context_aware_desc) - ) -} \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/hints/VocabularyProgressHint.kt b/app/src/main/java/eu/gaudian/translator/view/hints/VocabularyProgressHint.kt index 9ac2aeb..deea891 100644 --- a/app/src/main/java/eu/gaudian/translator/view/hints/VocabularyProgressHint.kt +++ b/app/src/main/java/eu/gaudian/translator/view/hints/VocabularyProgressHint.kt @@ -1,127 +1,33 @@ package eu.gaudian.translator.view.hints -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import eu.gaudian.translator.R -import eu.gaudian.translator.view.composable.AppIcons /** - * TODO: Migrate to markdown-based hint format. - * Create a .md file in assets/hints/ and use MarkdownHint composable instead. - * See MarkdownHint.kt for implementation details. + * Migrated Vocabulary Progress hint using markdown-based format. + * Content is loaded from localized assets/hints based on device locale. */ -object VocabularyProgressHint : LegacyHint() { - override val titleRes: Int = R.string.hint_vocabulary_progress_hint_title - - @Composable - override fun Content() { - Column( - modifier = Modifier.padding(16.dp), - verticalArrangement = Arrangement.spacedBy(16.dp) - ) { - HeaderWithIcon( - title = stringResource(R.string.hint_vocabulary_progress_hint_title) - ) - - HintSection(title = stringResource(R.string.hint_vocabulary_progress_tracking_title)) { - // Progress Tracking - VocabularyProgressHintItem( - icon = AppIcons.BarChart, - title = stringResource(R.string.hint_vocabulary_progress_tracking_title), - description = stringResource(R.string.hint_vocabulary_progress_tracking_desc) - ) - - // Learning Stages - VocabularyProgressHintItem( - icon = AppIcons.Stages, - title = stringResource(R.string.hint_vocabulary_learning_stages_title), - description = stringResource(R.string.hint_vocabulary_learning_stages_desc) - ) - - // Review System - VocabularyProgressHintItem( - icon = AppIcons.History, - title = stringResource(R.string.hint_vocabulary_review_system_title), - description = stringResource(R.string.hint_vocabulary_review_system_desc) - ) - - // Customization - VocabularyProgressHintItem( - icon = AppIcons.Tune, - title = stringResource(R.string.hint_vocabulary_customization_title), - description = stringResource(R.string.hint_vocabulary_customization_desc) - ) - } - } - } -} - -@Preview @Composable -private fun ContentPreview() { - VocabularyProgressHint.Content() +fun getVocabularyProgressHint(): Hint { + return Hint( + titleRes = R.string.hint_vocabulary_progress_hint_title, + elements = listOf( + HintElement.LocalizedMarkdown("vocabulary_progress_hint") + ) + ) } @Composable fun VocabularyProgressHint() { - VocabularyProgressHint.Content() + getVocabularyProgressHint().Render() } @Preview @Composable -private fun VocabularyProgressHintPreview() { - VocabularyProgressHint() -} - - -@Composable -private fun VocabularyProgressHintItem( - icon: androidx.compose.ui.graphics.vector.ImageVector, - title: String, - description: String -) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(16.dp) - ) { - Icon( - imageVector = icon, - contentDescription = title, - modifier = Modifier.size(32.dp), - tint = MaterialTheme.colorScheme.secondary - ) - Column { - Text(text = title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) - Spacer(modifier = Modifier.height(4.dp)) - Text(text = description, style = MaterialTheme.typography.bodyMedium) - } +fun VocabularyProgressHintPreview() { + MaterialTheme { + VocabularyProgressHint() } } - -@Suppress("HardCodedStringLiteral") -@Preview -@Composable -private fun VocabularyProgressHintItemPreview() { - VocabularyProgressHintItem( - icon = AppIcons.BarChart, - title = "Progress Tracking", - description = "Track your learning progress with detailed statistics." - ) -} \ No newline at end of file diff --git a/app/src/main/java/eu/gaudian/translator/view/settings/AddModelScreen.kt b/app/src/main/java/eu/gaudian/translator/view/settings/AddModelScreen.kt index 3597728..069c927 100644 --- a/app/src/main/java/eu/gaudian/translator/view/settings/AddModelScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/settings/AddModelScreen.kt @@ -59,6 +59,7 @@ import eu.gaudian.translator.view.composable.AppSwitch import eu.gaudian.translator.view.composable.AppTopAppBar import eu.gaudian.translator.view.composable.ModelBadges import eu.gaudian.translator.view.hints.AddModelScanHint +import eu.gaudian.translator.view.hints.getAddModelScanHint import eu.gaudian.translator.viewmodel.ApiViewModel @Composable @@ -140,7 +141,7 @@ fun AddModelScreen(navController: NavController, providerKey: String) { Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back)) } }, - hintContent = { AddModelScanHint() } + hintContent = getAddModelScanHint() ) }, ) { paddingValues -> diff --git a/app/src/main/java/eu/gaudian/translator/view/settings/ApiKeyScreen.kt b/app/src/main/java/eu/gaudian/translator/view/settings/ApiKeyScreen.kt index 366c14a..92112c1 100644 --- a/app/src/main/java/eu/gaudian/translator/view/settings/ApiKeyScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/settings/ApiKeyScreen.kt @@ -80,7 +80,7 @@ import eu.gaudian.translator.view.composable.ClickableText import eu.gaudian.translator.view.composable.PrimaryButton import eu.gaudian.translator.view.composable.SecondaryButton import eu.gaudian.translator.view.composable.TabItem -import eu.gaudian.translator.view.hints.ApiKeyHint +import eu.gaudian.translator.view.hints.getApiKeyHint import eu.gaudian.translator.viewmodel.ApiKeyManagementState import eu.gaudian.translator.viewmodel.ApiViewModel import eu.gaudian.translator.viewmodel.ProviderState @@ -121,7 +121,7 @@ fun ApiKeyScreen(navController: NavController) { Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back)) } }, - hintContent = { ApiKeyHint.Content() } + hintContent = getApiKeyHint() ) } ) { paddingValues -> diff --git a/app/src/main/java/eu/gaudian/translator/view/settings/CustomPromptScreens.kt b/app/src/main/java/eu/gaudian/translator/view/settings/CustomPromptScreens.kt index 0555413..ad97dca 100644 --- a/app/src/main/java/eu/gaudian/translator/view/settings/CustomPromptScreens.kt +++ b/app/src/main/java/eu/gaudian/translator/view/settings/CustomPromptScreens.kt @@ -61,12 +61,8 @@ fun CustomVocabularyPromptScreen( Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back)) } }, - hintContent = { - Text( - //TODO make this nicer and own file - stringResource(R.string.hint_this_screen_lets_you_customize_) - ) - } + hintContent = null //TODO: Add hint + ) } ) { paddingValues -> diff --git a/app/src/main/java/eu/gaudian/translator/view/settings/DictionaryOptionsScreen.kt b/app/src/main/java/eu/gaudian/translator/view/settings/DictionaryOptionsScreen.kt index b47f4c7..8e91f6f 100644 --- a/app/src/main/java/eu/gaudian/translator/view/settings/DictionaryOptionsScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/settings/DictionaryOptionsScreen.kt @@ -72,7 +72,7 @@ fun DictionaryOptionsScreen( Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back)) } }, - hintContent = { getDictionaryOptionsHint() } + hintContent = getDictionaryOptionsHint() ) } ) { paddingValues -> diff --git a/app/src/main/java/eu/gaudian/translator/view/settings/TranslationSettings.kt b/app/src/main/java/eu/gaudian/translator/view/settings/TranslationSettings.kt index 7bbb3a6..7efbeeb 100644 --- a/app/src/main/java/eu/gaudian/translator/view/settings/TranslationSettings.kt +++ b/app/src/main/java/eu/gaudian/translator/view/settings/TranslationSettings.kt @@ -70,12 +70,7 @@ fun TranslationSettingsScreen( Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back)) } }, - hintContent = { - Text( - //TODO make this nicer and an own file - stringResource(R.string.hint_use_this_screen_to_define) - ) - } + hintContent = null //TODO add hint ) } ) { paddingValues -> diff --git a/app/src/main/java/eu/gaudian/translator/view/settings/VocabularyProgressOptionsScreen.kt b/app/src/main/java/eu/gaudian/translator/view/settings/VocabularyProgressOptionsScreen.kt index 09efe94..0f3827d 100644 --- a/app/src/main/java/eu/gaudian/translator/view/settings/VocabularyProgressOptionsScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/settings/VocabularyProgressOptionsScreen.kt @@ -50,6 +50,7 @@ import eu.gaudian.translator.view.composable.AppScaffold import eu.gaudian.translator.view.composable.AppSlider import eu.gaudian.translator.view.composable.AppTopAppBar import eu.gaudian.translator.view.hints.VocabularyProgressHint +import eu.gaudian.translator.view.hints.getVocabularyProgressHint import eu.gaudian.translator.viewmodel.SettingsViewModel import eu.gaudian.translator.viewmodel.VocabularyViewModel import kotlin.math.exp @@ -85,7 +86,7 @@ fun VocabularyProgressOptionsScreen( } }, // Here is the new hint content - hintContent = { VocabularyProgressHint() } + hintContent = getVocabularyProgressHint() ) } ) { paddingValues -> diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularySortingScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularySortingScreen.kt index 6f4bd84..bf57276 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularySortingScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/VocabularySortingScreen.kt @@ -74,7 +74,7 @@ import eu.gaudian.translator.view.composable.AppTopAppBar import eu.gaudian.translator.view.composable.SingleLanguageDropDown import eu.gaudian.translator.view.dialogs.CategoryDropdown import eu.gaudian.translator.view.dialogs.CreateCategoryListDialog -import eu.gaudian.translator.view.hints.SortingScreenHint +import eu.gaudian.translator.view.hints.getSortingScreenHint import eu.gaudian.translator.viewmodel.CategoryViewModel import eu.gaudian.translator.viewmodel.LanguageConfigViewModel import eu.gaudian.translator.viewmodel.LanguageViewModel @@ -236,7 +236,7 @@ fun VocabularySortingScreen( Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back)) } }, - hintContent = {SortingScreenHint.Content()} + hintContent = getSortingScreenHint() ) },