migrate hints system to a localized markdown-based architecture and refactor related UI components
This commit is contained in:
53
app/src/main/assets/hints/api_key_hint.md
Normal file
53
app/src/main/assets/hints/api_key_hint.md
Normal 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.
|
||||
49
app/src/main/assets/hints/category_hint.md
Normal file
49
app/src/main/assets/hints/category_hint.md
Normal 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.*
|
||||
49
app/src/main/assets/hints/dictionary_hint.md
Normal file
49
app/src/main/assets/hints/dictionary_hint.md
Normal 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!*
|
||||
53
app/src/main/assets/hints/import_hint.md
Normal file
53
app/src/main/assets/hints/import_hint.md
Normal 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.*
|
||||
53
app/src/main/assets/hints/learning_stages_hint.md
Normal file
53
app/src/main/assets/hints/learning_stages_hint.md
Normal 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!*
|
||||
63
app/src/main/assets/hints/review_hint.md
Normal file
63
app/src/main/assets/hints/review_hint.md
Normal 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!*
|
||||
72
app/src/main/assets/hints/sorting_hint.md
Normal file
72
app/src/main/assets/hints/sorting_hint.md
Normal 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.*
|
||||
67
app/src/main/assets/hints/translation_hint.md
Normal file
67
app/src/main/assets/hints/translation_hint.md
Normal 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!*
|
||||
78
app/src/main/assets/hints/vocabulary_progress_hint.md
Normal file
78
app/src/main/assets/hints/vocabulary_progress_hint.md
Normal 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!*
|
||||
@@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -69,7 +69,7 @@ fun VocabularyReviewScreen(
|
||||
topBar = {
|
||||
AppTopAppBar(
|
||||
title = { Text(stringResource(R.string.found_items)) },
|
||||
hintContent = { getVocabularyReviewHint() }
|
||||
hintContent = getVocabularyReviewHint()
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
|
||||
@@ -1,145 +1,21 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SuggestionChip
|
||||
import androidx.compose.material3.SuggestionChipDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
|
||||
/**
|
||||
* Transformed AddModelScanHint using the new uniform Hint structure.
|
||||
* Migrated AddModelScanHint using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun getAddModelScanHint(): Hint {
|
||||
return Hint(
|
||||
titleRes = R.string.hint_scan_hint_title,
|
||||
elements = listOf(
|
||||
|
||||
HintElement.Section(
|
||||
title = stringResource(R.string.scan_hint_section_how_scan_works),
|
||||
content = listOf(
|
||||
HintElement.Text(stringResource(R.string.scan_hint_how_scan_works_paragraph)),
|
||||
HintElement.UIElement { Spacer(Modifier.height(8.dp)) },
|
||||
HintElement.UIElement { ReusedScanButtonPreview() },
|
||||
HintElement.UIElement { Spacer(Modifier.height(8.dp)) },
|
||||
HintElement.BulletList(
|
||||
listOf(
|
||||
stringResource(R.string.scan_hint_bullet_results_depend),
|
||||
stringResource(R.string.scan_hint_bullet_public_private),
|
||||
stringResource(R.string.scan_hint_bullet_try_again)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
HintElement.Divider,
|
||||
HintElement.Section(
|
||||
title = stringResource(R.string.scan_hint_section_why_missing),
|
||||
content = listOf(
|
||||
HintElement.InfoBadge(
|
||||
icon = AppIcons.Lock,
|
||||
text = stringResource(R.string.scan_hint_badge_restricted)
|
||||
),
|
||||
HintElement.InfoBadge(
|
||||
icon = AppIcons.Warning,
|
||||
text = stringResource(R.string.scan_hint_badge_not_suitable)
|
||||
),
|
||||
HintElement.InfoBadge(
|
||||
icon = AppIcons.CheckCircle,
|
||||
text = stringResource(R.string.scan_hint_badge_only_text_models)
|
||||
),
|
||||
HintElement.Divider,
|
||||
HintElement.Text(stringResource(R.string.scan_hint_focus_text_models)),
|
||||
HintElement.Divider,
|
||||
HintElement.Card {
|
||||
PerformanceTierChips()
|
||||
},
|
||||
HintElement.Divider,
|
||||
HintElement.InfoBadge(
|
||||
icon = AppIcons.Info,
|
||||
text = stringResource(R.string.scan_hint_most_tasks_small_models)
|
||||
)
|
||||
)
|
||||
),
|
||||
HintElement.Divider,
|
||||
HintElement.Section(
|
||||
title = stringResource(R.string.scan_hint_section_tips),
|
||||
content = listOf(
|
||||
HintElement.BulletList(
|
||||
listOf(
|
||||
stringResource(R.string.scan_hint_tip_verify_key),
|
||||
stringResource(R.string.scan_hint_tip_select_org),
|
||||
stringResource(R.string.scan_hint_tip_type_manually),
|
||||
stringResource(R.string.scan_hint_tip_instruct_chat_text)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
HintElement.Divider,
|
||||
HintElement.Section(
|
||||
title = stringResource(R.string.hint_scan_hint_section_visual_guide),
|
||||
content = listOf(
|
||||
HintElement.VisualStep(
|
||||
step = stringResource(R.string.hint_scan_hint_step_1),
|
||||
title = stringResource(R.string.hint_scan_hint_step1_title),
|
||||
description = stringResource(R.string.hint_scan_hint_step1_desc),
|
||||
trailing = {
|
||||
Icon(AppIcons.Search, contentDescription = null, tint = MaterialTheme.colorScheme.primary)
|
||||
}
|
||||
),
|
||||
HintElement.VisualStep(
|
||||
step = stringResource(R.string.hint_scan_hint_step_2),
|
||||
title = stringResource(R.string.hint_scan_hint_step2_title),
|
||||
description = stringResource(R.string.hint_scan_hint_step2_desc),
|
||||
trailing = {
|
||||
SuggestionChip(
|
||||
onClick = {},
|
||||
label = { Text(stringResource(R.string.hint_scan_hint_label_text_chat)) },
|
||||
enabled = false,
|
||||
icon = {},
|
||||
colors = SuggestionChipDefaults.suggestionChipColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
labelColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
)
|
||||
}
|
||||
),
|
||||
HintElement.VisualStep(
|
||||
step = stringResource(R.string.hint_scan_hint_step_3),
|
||||
title = stringResource(R.string.hint_scan_hint_step3_title),
|
||||
description = stringResource(R.string.hint_scan_hint_step3_desc),
|
||||
trailing = {
|
||||
SmallPrimaryCard(text = stringResource(R.string.hint_scan_hint_add_validate))
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
HintElement.Divider,
|
||||
HintElement.Section(
|
||||
title = stringResource(R.string.hint_scan_hint_section_cant_find),
|
||||
content = listOf(
|
||||
HintElement.Text(stringResource(R.string.hint_scan_hint_manual_add_paragraph))
|
||||
)
|
||||
titleRes = R.string.hint_scan_hint_title,
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("example_hint")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ReusedScanButtonPreviewPreview() {
|
||||
ReusedScanButtonPreview()
|
||||
}
|
||||
|
||||
@Preview
|
||||
|
||||
@@ -1,130 +1,33 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
|
||||
/**
|
||||
* TODO: Migrate to markdown-based hint format.
|
||||
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
|
||||
* See MarkdownHint.kt for implementation details.
|
||||
* Migrated API Key hint using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
object ApiKeyHint: LegacyHint() {
|
||||
override val titleRes: Int = R.string.hint_how_to_connect_to_an_ai
|
||||
|
||||
@Composable
|
||||
override fun getTitle(): String = stringResource(R.string.hint_how_to_connect_to_an_ai)
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
HeaderWithIcon(
|
||||
title = stringResource(R.string.hint_how_to_connect_to_an_ai)
|
||||
)
|
||||
|
||||
HintSection(title = stringResource(R.string.connecting_your_ai_model)) {
|
||||
Text(
|
||||
text = stringResource(R.string.api_hint_intro_1),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.api_hint_intro_2),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
|
||||
HintSection(title = stringResource(R.string.key_status_indicators_title)) {
|
||||
Text(
|
||||
text = stringResource(R.string.key_status_explanation),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
KeyStatus(hasKey = true)
|
||||
Text(
|
||||
text = stringResource(R.string.key_saved_and_active),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(start = 20.dp, bottom = 8.dp)
|
||||
)
|
||||
KeyStatus(hasKey = false)
|
||||
Text(
|
||||
text = stringResource(R.string.key_missing_or_cleared),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
modifier = Modifier.padding(start = 20.dp)
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
|
||||
HintSection(title = stringResource(R.string.troubleshooting_title)) {
|
||||
Text(
|
||||
text = stringResource(R.string.troubleshooting_intro),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.troubleshooting_bullets),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
fun getApiKeyHint() = Hint (
|
||||
titleRes = R.string.hint_how_to_connect_to_an_ai,
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("api_key_hint")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@Composable
|
||||
private fun KeyStatus(hasKey: Boolean) {
|
||||
val (text, color, icon) = if (hasKey) {
|
||||
Triple(
|
||||
stringResource(R.string.text_key_active),
|
||||
MaterialTheme.colorScheme.primary,
|
||||
AppIcons.Check
|
||||
)
|
||||
} else {
|
||||
Triple(
|
||||
stringResource(R.string.text_no_key),
|
||||
MaterialTheme.colorScheme.error,
|
||||
AppIcons.Warning
|
||||
)
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = text,
|
||||
tint = color,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(text, color = color, style = MaterialTheme.typography.labelLarge)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ApiKeyHint() {
|
||||
getApiKeyHint().Render()
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview
|
||||
@Composable
|
||||
fun ApiKeyHintPreview() {
|
||||
MaterialTheme {
|
||||
ApiKeyHint.Content()
|
||||
ApiKeyHint()
|
||||
}
|
||||
}
|
||||
@@ -1,121 +1,33 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
|
||||
/**
|
||||
* TODO: Migrate to markdown-based hint format.
|
||||
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
|
||||
* See MarkdownHint.kt for implementation details.
|
||||
* Migrated Category hint using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
object CategoryHint : LegacyHint() {
|
||||
override val titleRes: Int = R.string.category_hint_intro
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
HeaderWithIcon(
|
||||
title = stringResource(R.string.category_hint_intro)
|
||||
)
|
||||
|
||||
HintSection(title = stringResource(R.string.category_hint_intro)) {
|
||||
// Tag Category Explanation (formerly List)
|
||||
CategoryHintItem(
|
||||
icon = {
|
||||
Icon(
|
||||
AppIcons.FilterList,
|
||||
contentDescription = stringResource(R.string.content_desc_tag_category),
|
||||
modifier = Modifier.size(32.dp),
|
||||
tint = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.text_list),
|
||||
description = stringResource(R.string.hint_list_category)
|
||||
)
|
||||
|
||||
// Filter Category Explanation
|
||||
CategoryHintItem(
|
||||
icon = {
|
||||
Icon(
|
||||
AppIcons.FilterCategory,
|
||||
contentDescription = stringResource(R.string.content_desc_filter_category),
|
||||
modifier = Modifier.size(32.dp),
|
||||
tint = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
},
|
||||
title = stringResource(R.string.text_filter),
|
||||
description = stringResource(R.string.hint_filter_category_description)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
fun getCategoryHint(): Hint {
|
||||
return Hint(
|
||||
titleRes = R.string.category_hint_intro,
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("category_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CategoryHint() {
|
||||
CategoryHint.Content()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CategoryHintItem(
|
||||
icon: @Composable () -> Unit,
|
||||
title: String,
|
||||
description: String
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
icon()
|
||||
Column {
|
||||
Text(text = title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(text = description, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
getCategoryHint().Render()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun CategoryHintPreview() {
|
||||
CategoryHint()
|
||||
}
|
||||
|
||||
@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)
|
||||
)
|
||||
MaterialTheme {
|
||||
CategoryHint()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
HintSection(title = stringResource(R.string.category_list_title)) {
|
||||
Text(
|
||||
text = stringResource(R.string.category_list_description),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
// Visual representation for List Category
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
WordItem(text = stringResource(R.string.example_word_apple))
|
||||
Icon(AppIcons.Add, contentDescription = stringResource(R.string.action_add), tint = MaterialTheme.colorScheme.secondary)
|
||||
CategoryBox(icon = AppIcons.FilterList, text = stringResource(R.string.example_category_my_fruit_list))
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
HintSection(title = stringResource(R.string.category_filter_title)) {
|
||||
Text(
|
||||
text = stringResource(R.string.category_filter_description),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
// Visual representation for Filter Category
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Row {
|
||||
WordItem(text = stringResource(R.string.example_word_dog), subtext = stringResource(R.string.stage_1))
|
||||
Spacer(modifier = Modifier.padding(horizontal = 4.dp))
|
||||
WordItem(text = stringResource(R.string.example_word_cat), subtext = stringResource(R.string.stage_1))
|
||||
}
|
||||
CategoryBox(icon = AppIcons.FilterCategory, text = stringResource(R.string.example_filter_stage_1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
fun getCategoryHintScreen(): Hint {
|
||||
return Hint(
|
||||
titleRes = R.string.category_hint_intro,
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("category_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CategoryHintScreen() {
|
||||
CategoryHintScreen.Content()
|
||||
getCategoryHintScreen().Render()
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple visual representation of a vocabulary word.
|
||||
*/
|
||||
@Composable
|
||||
private fun WordItem(text: String, subtext: String? = null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
.padding(horizontal = 12.dp, vertical = 6.dp)
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(text = text, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.SemiBold)
|
||||
subtext?.let {
|
||||
Text(text = it, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple visual representation of a category.
|
||||
*/
|
||||
@Composable
|
||||
private fun CategoryBox(icon: ImageVector, text: String) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(MaterialTheme.colorScheme.secondaryContainer)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Icon(imageVector = icon, contentDescription = text, modifier = Modifier.size(24.dp))
|
||||
Text(text = text, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview
|
||||
@Composable
|
||||
fun CategoryHintScreenPreview() {
|
||||
MaterialTheme {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,38 +41,37 @@ fun HintsOverviewScreen(
|
||||
) {
|
||||
val showExperimental = LocalShowExperimentalFeatures.current
|
||||
|
||||
// Get hints using the new function-based approach
|
||||
val importHint = getImportVocabularyHint()
|
||||
val addModelScanHint = getAddModelScanHint()
|
||||
val dictionaryOptionsHint = getDictionaryOptionsHint()
|
||||
val translationScreenHint = getTranslationScreenHint()
|
||||
val categoryHint = getCategoryHint()
|
||||
val learningStagesHint = getLearningStagesHint()
|
||||
val sortingScreenHint = getSortingScreenHint()
|
||||
val vocabularyProgressHint = getVocabularyProgressHint()
|
||||
val apiKeyHint = getApiKeyHint()
|
||||
|
||||
|
||||
|
||||
|
||||
val hintGroups = remember(showExperimental, importHint) {
|
||||
val hintGroups = remember(showExperimental, importHint, addModelScanHint, dictionaryOptionsHint, translationScreenHint) {
|
||||
val allGroups = listOf(
|
||||
R.string.hint_hints_header_basics to listOf(
|
||||
HintItem(CategoryHint.titleRes, AppIcons.Category, SettingsRoutes.HINTS_CATEGORIES),
|
||||
HintItem(LearningStagesHint.titleRes, AppIcons.Stages, SettingsRoutes.HINTS_STAGES),
|
||||
HintItem(categoryHint.titleRes, AppIcons.Category, SettingsRoutes.HINTS_CATEGORIES),
|
||||
HintItem(learningStagesHint.titleRes, AppIcons.Stages, SettingsRoutes.HINTS_STAGES),
|
||||
HintItem(translationScreenHint.titleRes, AppIcons.Translate, SettingsRoutes.HINTS_TRANSLATION)
|
||||
),
|
||||
R.string.hint_hints_header_vocabulary to listOf(
|
||||
HintItem(importHint.titleRes, AppIcons.Vocabulary, SettingsRoutes.HINTS_IMPORT),
|
||||
HintItem(SortingScreenHint.titleRes, AppIcons.Sort, SettingsRoutes.HINTS_SORTING),
|
||||
HintItem(sortingScreenHint.titleRes, AppIcons.Sort, SettingsRoutes.HINTS_SORTING),
|
||||
HintItem(dictionaryOptionsHint.titleRes, AppIcons.Dictionary, SettingsRoutes.HINTS_DICTIONARY),
|
||||
HintItem(VocabularyProgressHint.titleRes, AppIcons.Stages, SettingsRoutes.HINTS_VOCABULARY_PROGRESS)
|
||||
HintItem(vocabularyProgressHint.titleRes, AppIcons.Stages, SettingsRoutes.HINTS_VOCABULARY_PROGRESS)
|
||||
),
|
||||
R.string.hint_hints_header_advanced to listOf(
|
||||
HintItem(addModelScanHint.titleRes, AppIcons.AI, SettingsRoutes.HINTS_SCAN),
|
||||
HintItem(ApiKeyHint.titleRes, AppIcons.ApiKey, SettingsRoutes.HINTS_API)
|
||||
HintItem(apiKeyHint.titleRes, AppIcons.ApiKey, SettingsRoutes.HINTS_API)
|
||||
)
|
||||
)
|
||||
|
||||
if (showExperimental) {
|
||||
allGroups
|
||||
} else {
|
||||
allGroups
|
||||
}
|
||||
allGroups
|
||||
}
|
||||
|
||||
AppOutlinedCard {
|
||||
|
||||
@@ -1,173 +1,45 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.view.composable.AppCheckbox
|
||||
import eu.gaudian.translator.view.composable.AppOutlinedTextField
|
||||
import eu.gaudian.translator.view.composable.AppSlider
|
||||
|
||||
/**
|
||||
* Provides the migrated Hint for ImportVocabulary.
|
||||
* This function is @Composable to access stringResource.
|
||||
* Migrated ImportVocabularyHint using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
@Composable
|
||||
fun getImportVocabularyHint(): Hint {
|
||||
return Hint(
|
||||
titleRes = R.string.hint_how_to_generate_vocabulary_with_ai,
|
||||
elements = listOf(
|
||||
HintElement.Section(
|
||||
title = stringResource(R.string.import_ai_intro),
|
||||
content = listOf(
|
||||
// VisualStep 1: Search Term
|
||||
HintElement.VisualStep(
|
||||
step = "1",
|
||||
title = stringResource(R.string.import_step1_title),
|
||||
description = stringResource(R.string.text_hint_you_can_search),
|
||||
trailing = {
|
||||
// The AppTextField is wrapped in the VisualStep's trailing composable
|
||||
AppOutlinedTextField(
|
||||
value = stringResource(R.string.search_term_placeholder),
|
||||
onValueChange = {},
|
||||
label = { Text(stringResource(R.string.text_search_term)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = false
|
||||
)
|
||||
}
|
||||
),
|
||||
// VisualStep 2: Select Languages
|
||||
HintElement.VisualStep(
|
||||
step = "2",
|
||||
title = stringResource(R.string.import_step2_title),
|
||||
description = stringResource(R.string.import_step2_desc)
|
||||
),
|
||||
// VisualStep 3: Select Amount
|
||||
HintElement.VisualStep(
|
||||
step = "3",
|
||||
title = stringResource(R.string.import_step3_title),
|
||||
description = stringResource(R.string.import_step3_desc),
|
||||
trailing = {
|
||||
// The Column with Slider and Text is wrapped in the trailing composable
|
||||
Column {
|
||||
AppSlider(
|
||||
value = 10f,
|
||||
onValueChange = {},
|
||||
valueRange = 1f..25f,
|
||||
steps = 24,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
enabled = false
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.import_after_generating),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("import_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview for the migrated ImportVocabularyHint.
|
||||
* It now calls getImportVocabularyHint() and then Render().
|
||||
*/
|
||||
@Preview
|
||||
@Composable
|
||||
fun ImportVocabularyHintPreview() {
|
||||
fun ImportVocabularyHint() {
|
||||
getImportVocabularyHint().Render()
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the migrated Hint for VocabularyReview.
|
||||
* This function is @Composable to access stringResource.
|
||||
* Migrated VocabularyReviewHint using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun getVocabularyReviewHint(): Hint {
|
||||
return Hint(
|
||||
titleRes = R.string.review_intro,
|
||||
elements = listOf(
|
||||
// Section 1: Select Items
|
||||
HintElement.Section(
|
||||
title = stringResource(R.string.review_select_items_title),
|
||||
content = listOf(
|
||||
HintElement.Text(stringResource(R.string.review_select_items_desc)),
|
||||
// Custom Row with Checkbox is wrapped in a UIElement
|
||||
HintElement.UIElement {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AppCheckbox(checked = true, onCheckedChange = {}, enabled = false)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Column {
|
||||
Text(text = stringResource(R.string.example_word_der_apfel), style = MaterialTheme.typography.bodyLarge)
|
||||
Text(text = stringResource(R.string.example_word_the_apple), style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
// Divider
|
||||
HintElement.Divider,
|
||||
// Section 2: Duplicate Handling
|
||||
HintElement.Section(
|
||||
title = stringResource(R.string.duplicate_handling_title),
|
||||
content = listOf(
|
||||
HintElement.Text(stringResource(R.string.duplicate_handling_desc)),
|
||||
// Custom Row with Checkbox and Error Text is wrapped in a UIElement
|
||||
HintElement.UIElement {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AppCheckbox(checked = false, onCheckedChange = {}, enabled = false)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Column {
|
||||
Text(text = stringResource(R.string.example_word_der_hund), style = MaterialTheme.typography.bodyLarge)
|
||||
Text(text = stringResource(R.string.example_word_the_dog), style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Text(stringResource(R.string.duplicate), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
// Divider
|
||||
HintElement.Divider,
|
||||
// Section 3: Add to List
|
||||
HintElement.Section(
|
||||
title = stringResource(R.string.add_to_list_optional),
|
||||
content = listOf(
|
||||
HintElement.Text(stringResource(R.string.add_to_list_optional_desc))
|
||||
)
|
||||
)
|
||||
HintElement.LocalizedMarkdown("review_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview for the migrated VocabularyReviewHint.
|
||||
* It now calls getVocabularyReviewHint() and then Render().
|
||||
*/
|
||||
@Preview
|
||||
|
||||
@Composable
|
||||
fun VocabularyReviewHintPreview() {
|
||||
getVocabularyReviewHint().Render()
|
||||
fun VocabularyReviewHint() {
|
||||
getVocabularyReviewHint()
|
||||
}
|
||||
@@ -1,205 +1,33 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.PathEffect
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
|
||||
private data class LearningStage(
|
||||
val icon: ImageVector,
|
||||
val name: String,
|
||||
val interval: String? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* TODO: Migrate to markdown-based hint format.
|
||||
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
|
||||
* See MarkdownHint.kt for implementation details.
|
||||
* Migrated Learning Stages hint using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
object LearningStagesHint : LegacyHint() {
|
||||
override val titleRes: Int = R.string.learning_stages_title
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val stages = listOf(
|
||||
LearningStage(AppIcons.StageNew, stringResource(R.string.stage_new)),
|
||||
LearningStage(AppIcons.Stage1, stringResource(R.string.stage_1), stringResource(R.string.interval_1_day)),
|
||||
LearningStage(AppIcons.Stage2, stringResource(R.string.stage_2), stringResource(R.string.interval_3_days)),
|
||||
LearningStage(AppIcons.Stage3, stringResource(R.string.stage_3), stringResource(R.string.interval_1_week)),
|
||||
LearningStage(AppIcons.Stage4, stringResource(R.string.stage_4), stringResource(R.string.interval_2_weeks)),
|
||||
LearningStage(AppIcons.Stage5, stringResource(R.string.stage_5), stringResource(R.string.interval_1_month)),
|
||||
LearningStage(AppIcons.StageLearned, stringResource(R.string.stage_learned))
|
||||
@Composable
|
||||
fun getLearningStagesHint(): Hint {
|
||||
return Hint(
|
||||
titleRes = R.string.learning_stages_title,
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("learning_stages_hint")
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
HeaderWithIcon(
|
||||
title = stringResource(R.string.learning_stages_title)
|
||||
)
|
||||
|
||||
HintSection(title = stringResource(R.string.learning_stages_title)) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Group stages into chunks of 2 to create rows.
|
||||
stages.chunked(2).forEach { rowItems ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
rowItems.forEach { stage ->
|
||||
StageNode(stage)
|
||||
// If the stage has an interval, show the connector arrow
|
||||
stage.interval?.let { interval ->
|
||||
StageConnector(interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HintSection(title = stringResource(R.string.hint_how_it_works)) {
|
||||
RuleExplanation(
|
||||
icon = AppIcons.Check,
|
||||
title = stringResource(R.string.hint_answer_correctly),
|
||||
description = stringResource(R.string.hint_the_word_moves),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
RuleExplanation(
|
||||
icon = AppIcons.Error,
|
||||
title = stringResource(R.string.hint_answer_incorrectly),
|
||||
description = stringResource(R.string.hint_the_word_moves_back_another_stage_this_helps_you_focus_on_),
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
RuleExplanation(
|
||||
icon = AppIcons.Info,
|
||||
title = stringResource(R.string.hint_customizable),
|
||||
description = stringResource(R.string.hint_you_can_costumize_all_intervals_and_rules_in_the_settings),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LearningStagesHint() {
|
||||
LearningStagesHint.Content()
|
||||
getLearningStagesHint().Render()
|
||||
}
|
||||
|
||||
/**
|
||||
* A composable that displays a single stage icon and its name.
|
||||
*/
|
||||
@Composable
|
||||
private fun StageNode(stage: LearningStage) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = stage.icon,
|
||||
contentDescription = stage.name,
|
||||
modifier = Modifier.size(32.dp),
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
Text(
|
||||
text = stage.name,
|
||||
style = MaterialTheme.typography.labelMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A composable that draws a dashed arrow and displays the time interval between stages.
|
||||
*/
|
||||
@Composable
|
||||
private fun StageConnector(interval: String) {
|
||||
val arrowColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.padding(horizontal = 8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = interval,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Canvas(modifier = Modifier.size(width = 50.dp, height = 10.dp)) {
|
||||
val startY = center.y
|
||||
val endX = size.width
|
||||
// Draw the dashed line
|
||||
drawLine(
|
||||
color = arrowColor,
|
||||
start = Offset(0f, startY),
|
||||
end = Offset(endX - 5.dp.toPx(), startY),
|
||||
strokeWidth = 1.5.dp.toPx(),
|
||||
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
|
||||
)
|
||||
// Draw the arrowhead
|
||||
drawLine(arrowColor, Offset(endX - 5.dp.toPx(), startY - 4.dp.toPx()), Offset(endX, startY), strokeWidth = 1.5.dp.toPx())
|
||||
drawLine(arrowColor, Offset(endX - 5.dp.toPx(), startY + 4.dp.toPx()), Offset(endX, startY), strokeWidth = 1.5.dp.toPx())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper composable to explain the success/failure rules.
|
||||
*/
|
||||
@Composable
|
||||
private fun RuleExplanation(icon: ImageVector, title: String, description: String, tint: androidx.compose.ui.graphics.Color) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = title,
|
||||
modifier = Modifier.size(28.dp),
|
||||
tint = tint
|
||||
)
|
||||
Column {
|
||||
Text(text = title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
||||
Text(text = description, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview
|
||||
@Composable
|
||||
fun LearningStagesHintPreview() {
|
||||
MaterialTheme {
|
||||
Box(modifier = Modifier.padding(8.dp)) {
|
||||
LearningStagesHint()
|
||||
}
|
||||
LearningStagesHint()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
@@ -1,127 +1,33 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
|
||||
/**
|
||||
* TODO: Migrate to markdown-based hint format.
|
||||
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
|
||||
* See MarkdownHint.kt for implementation details.
|
||||
* Migrated Vocabulary Progress hint using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
object VocabularyProgressHint : LegacyHint() {
|
||||
override val titleRes: Int = R.string.hint_vocabulary_progress_hint_title
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
HeaderWithIcon(
|
||||
title = stringResource(R.string.hint_vocabulary_progress_hint_title)
|
||||
)
|
||||
|
||||
HintSection(title = stringResource(R.string.hint_vocabulary_progress_tracking_title)) {
|
||||
// Progress Tracking
|
||||
VocabularyProgressHintItem(
|
||||
icon = AppIcons.BarChart,
|
||||
title = stringResource(R.string.hint_vocabulary_progress_tracking_title),
|
||||
description = stringResource(R.string.hint_vocabulary_progress_tracking_desc)
|
||||
)
|
||||
|
||||
// Learning Stages
|
||||
VocabularyProgressHintItem(
|
||||
icon = AppIcons.Stages,
|
||||
title = stringResource(R.string.hint_vocabulary_learning_stages_title),
|
||||
description = stringResource(R.string.hint_vocabulary_learning_stages_desc)
|
||||
)
|
||||
|
||||
// Review System
|
||||
VocabularyProgressHintItem(
|
||||
icon = AppIcons.History,
|
||||
title = stringResource(R.string.hint_vocabulary_review_system_title),
|
||||
description = stringResource(R.string.hint_vocabulary_review_system_desc)
|
||||
)
|
||||
|
||||
// Customization
|
||||
VocabularyProgressHintItem(
|
||||
icon = AppIcons.Tune,
|
||||
title = stringResource(R.string.hint_vocabulary_customization_title),
|
||||
description = stringResource(R.string.hint_vocabulary_customization_desc)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ContentPreview() {
|
||||
VocabularyProgressHint.Content()
|
||||
fun getVocabularyProgressHint(): Hint {
|
||||
return Hint(
|
||||
titleRes = R.string.hint_vocabulary_progress_hint_title,
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("vocabulary_progress_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun VocabularyProgressHint() {
|
||||
VocabularyProgressHint.Content()
|
||||
getVocabularyProgressHint().Render()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun VocabularyProgressHintPreview() {
|
||||
VocabularyProgressHint()
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun VocabularyProgressHintItem(
|
||||
icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||
title: String,
|
||||
description: String
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = title,
|
||||
modifier = Modifier.size(32.dp),
|
||||
tint = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
Column {
|
||||
Text(text = title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(text = description, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
fun VocabularyProgressHintPreview() {
|
||||
MaterialTheme {
|
||||
VocabularyProgressHint()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
@Preview
|
||||
@Composable
|
||||
private fun VocabularyProgressHintItemPreview() {
|
||||
VocabularyProgressHintItem(
|
||||
icon = AppIcons.BarChart,
|
||||
title = "Progress Tracking",
|
||||
description = "Track your learning progress with detailed statistics."
|
||||
)
|
||||
}
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -72,7 +72,7 @@ fun DictionaryOptionsScreen(
|
||||
Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back))
|
||||
}
|
||||
},
|
||||
hintContent = { getDictionaryOptionsHint() }
|
||||
hintContent = getDictionaryOptionsHint()
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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()
|
||||
)
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user