migrate hints system to a localized markdown-based architecture and refactor related UI components

This commit is contained in:
jonasgaudian
2026-02-14 17:15:26 +01:00
parent 306d0c7432
commit d2e77083ad
35 changed files with 1035 additions and 1944 deletions

View File

@@ -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.

View File

@@ -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.*

View File

@@ -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!*

View File

@@ -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.*

View File

@@ -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!*

View File

@@ -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!*

View File

@@ -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.*

View File

@@ -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!*

View File

@@ -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!*

View File

@@ -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()
}
)
}
}

View File

@@ -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

View File

@@ -69,7 +69,7 @@ fun VocabularyReviewScreen(
topBar = {
AppTopAppBar(
title = { Text(stringResource(R.string.found_items)) },
hintContent = { getVocabularyReviewHint() }
hintContent = getVocabularyReviewHint()
)
},
) { paddingValues ->

View File

@@ -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.LocalizedMarkdown("example_hint")
)
)
)
),
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))
)
)
)
)
}
@Preview
@Composable
fun ReusedScanButtonPreviewPreview() {
ReusedScanButtonPreview()
}
@Preview

View File

@@ -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)
@Composable
fun getApiKeyHint() = Hint (
titleRes = R.string.hint_how_to_connect_to_an_ai,
elements = listOf(
HintElement.LocalizedMarkdown("api_key_hint")
)
)
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
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()
}
}

View File

@@ -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)
@Composable
fun getCategoryHint(): Hint {
return Hint(
titleRes = R.string.category_hint_intro,
elements = listOf(
HintElement.LocalizedMarkdown("category_hint")
)
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 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() {
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)
)
}
}

View File

@@ -1,133 +1,30 @@
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)
@Composable
fun getCategoryHintScreen(): Hint {
return Hint(
titleRes = R.string.category_hint_intro,
elements = listOf(
HintElement.LocalizedMarkdown("category_hint")
)
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 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 {

View File

@@ -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()
}

View File

@@ -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")
}
}
}

View File

@@ -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) {
@@ -100,225 +71,3 @@ 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<String>) {
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")
}

View File

@@ -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>) : HintElement()
data class TextSection(val title: String, val text: String) : HintElement()
/**
* A bullet list element.
*/
data class BulletList(val items: List<String>) : 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<String>) : 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<String>) {
// 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
@@ -259,3 +135,12 @@ fun UIElementPreview() {
}
)
}
@Suppress("HardCodedStringLiteral")
@Preview
@Composable
fun LocalizedMarkdownElementPreview() {
RenderHintElement(
HintElement.LocalizedMarkdown("example_hint")
)
}

View File

@@ -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()
}
}

View File

@@ -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
}
}
AppOutlinedCard {

View File

@@ -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
)
}
}
)
)
)
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()
fun VocabularyReviewHint() {
getVocabularyReviewHint()
}

View File

@@ -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()
}
}
}

View File

@@ -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<Locale> {
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<LocalizedHint> {
val available = mutableListOf<LocalizedHint>()
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)
}
}

View File

@@ -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()
fun SortingScreenHintPreview() {
MaterialTheme {
SortingScreenHint()
}
}

View File

@@ -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)
)
}

View File

@@ -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() {
fun VocabularyProgressHintPreview() {
MaterialTheme {
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)
}
}
}
@Suppress("HardCodedStringLiteral")
@Preview
@Composable
private fun VocabularyProgressHintItemPreview() {
VocabularyProgressHintItem(
icon = AppIcons.BarChart,
title = "Progress Tracking",
description = "Track your learning progress with detailed statistics."
)
}

View File

@@ -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 ->

View File

@@ -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 ->

View File

@@ -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 ->

View File

@@ -72,7 +72,7 @@ fun DictionaryOptionsScreen(
Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back))
}
},
hintContent = { getDictionaryOptionsHint() }
hintContent = getDictionaryOptionsHint()
)
}
) { paddingValues ->

View File

@@ -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 ->

View File

@@ -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 ->

View File

@@ -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()
)
},