Compare commits
3 Commits
5e920c43b3
...
8e610259ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e610259ca | ||
|
|
7d18f8eb04 | ||
|
|
f4fcffe90a |
2
.idea/deploymentTargetSelector.xml
generated
2
.idea/deploymentTargetSelector.xml
generated
@@ -4,7 +4,7 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2026-02-06T10:01:23.649270100Z">
|
||||
<DropdownSelection timestamp="2026-02-15T19:51:37.987601800Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\jonas\.android\avd\Medium_Phone_28.avd" />
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
# How to Scan for AI Models
|
||||
# TODO REWRITE
|
||||
|
||||
This guide explains how to use the **Scan** feature to discover and add AI models to your app.
|
||||
|
||||
## How Scanning Works
|
||||
|
||||
The scan feature searches for available AI models on your device or network.
|
||||
|
||||
> **Note:** Results depend on your API key permissions.
|
||||
|
||||
### Key Points
|
||||
|
||||
- Only public models are shown by default
|
||||
- Private models require additional setup
|
||||
- Try again if no models are found
|
||||
|
||||
## Why Some Models Are Missing
|
||||
|
||||
Some models may not appear in the scan results due to:
|
||||
|
||||
| Reason | Description | Icon |
|
||||
|--------|-------------|------|
|
||||
| Restricted access | Model requires special permissions | 🔒 |
|
||||
| Not suitable | Model type not supported | ⚠️ |
|
||||
| Text only | Only text-based models are supported | ✓ |
|
||||
|
||||
### Model Tiers
|
||||
|
||||
We recommend these tiers for optimal performance:
|
||||
|
||||
- **Nano** - Fastest, for simple tasks
|
||||
- **Mini** - Balanced speed and capability
|
||||
- **Small** - Good for most tasks
|
||||
- **Medium** - More capable, slower
|
||||
- **Large** - Most capable, paid only
|
||||
|
||||
## Tips for Success
|
||||
|
||||
1. **Verify your API key** is active and has correct permissions
|
||||
2. **Select the correct organization** from your account
|
||||
3. **Type model names manually** if scanning doesn't find them
|
||||
4. **Prefer instruct or chat models** for text generation
|
||||
|
||||
```kotlin
|
||||
// Example: Manual model addition
|
||||
val model = Model(
|
||||
name = "llama3.2",
|
||||
type = ModelType.TEXT,
|
||||
provider = "ollama"
|
||||
)
|
||||
```
|
||||
|
||||
## Visual Guide
|
||||
|
||||
### Step 1: Initiate Scan
|
||||
|
||||
Click the scan button to search for available models.
|
||||
|
||||
### Step 2: Select Model Type
|
||||
|
||||
Choose between different model categories:
|
||||
|
||||
- **Text Chat** - For conversational AI
|
||||
- **Instruct** - For direct instructions
|
||||
- **Complete** - For text completion
|
||||
|
||||
### Step 3: Add & Validate
|
||||
|
||||
Add the selected model and validate it works correctly.
|
||||
|
||||
---
|
||||
|
||||
## Can't Find Your Model?
|
||||
|
||||
If your model doesn't appear in the scan results:
|
||||
|
||||
1. Check if the model is running locally or accessible via API
|
||||
2. Verify network connectivity
|
||||
3. Try adding it manually by entering the model details
|
||||
|
||||
> **Pro Tip:** You can always add models manually by clicking the "+" button in the models screen.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2024-01-15*
|
||||
*For more help, visit our documentation website.*
|
||||
@@ -21,10 +21,10 @@
|
||||
"description": "Next-gen efficient architecture; outperforms older 70B models."
|
||||
},
|
||||
{
|
||||
"modelId": "deepseek-ai/DeepSeek-V3",
|
||||
"displayName": "DeepSeek V3",
|
||||
"modelId": "deepseek-ai/DeepSeek-V3.1",
|
||||
"displayName": "DeepSeek V3.1",
|
||||
"provider": "together",
|
||||
"description": "Top-tier open-source model specializing in code and logic."
|
||||
"description": "Latest 671B MoE model with hybrid thinking/non-thinking modes."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -37,10 +37,10 @@
|
||||
"isCustom": false,
|
||||
"models": [
|
||||
{
|
||||
"modelId": "ministral-8b-latest",
|
||||
"displayName": "Ministral 8B",
|
||||
"modelId": "mistral-medium-latest",
|
||||
"displayName": "Mistral Medium",
|
||||
"provider": "mistral",
|
||||
"description": "Extremely efficient edge model for low-latency tasks."
|
||||
"description": "Balanced performance and cost for a wide range of tasks."
|
||||
},
|
||||
{
|
||||
"modelId": "mistral-large-latest",
|
||||
@@ -58,17 +58,17 @@
|
||||
"websiteUrl": "https://platform.openai.com/",
|
||||
"isCustom": false,
|
||||
"models": [
|
||||
{
|
||||
"modelId": "gpt-5.2",
|
||||
"displayName": "GPT-5.2",
|
||||
"provider": "openai",
|
||||
"description": "Balanced performance with enhanced reasoning and creativity."
|
||||
},
|
||||
{
|
||||
"modelId": "gpt-5.1-instant",
|
||||
"displayName": "GPT-5.1 Instant",
|
||||
"provider": "openai",
|
||||
"description": "The standard high-speed efficiency model replacing older 'Nano' tiers."
|
||||
},
|
||||
{
|
||||
"modelId": "gpt-5-nano",
|
||||
"displayName": "GPT-5 Nano",
|
||||
"provider": "openai",
|
||||
"description": "Fast and cheap model sufficient for most tasks."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -81,16 +81,16 @@
|
||||
"isCustom": false,
|
||||
"models": [
|
||||
{
|
||||
"modelId": "claude-sonnet-5-20260203",
|
||||
"displayName": "Claude Sonnet 5",
|
||||
"modelId": "claude-opus-4-6",
|
||||
"displayName": "Claude Opus 4.6",
|
||||
"provider": "anthropic",
|
||||
"description": "Latest stable workhorse (Feb 2026), balancing speed and top-tier reasoning."
|
||||
"description": "Most intelligent model for building agents and coding with 1M context."
|
||||
},
|
||||
{
|
||||
"modelId": "claude-4.5-haiku",
|
||||
"displayName": "Claude 4.5 Haiku",
|
||||
"modelId": "claude-sonnet-4-5",
|
||||
"displayName": "Claude Sonnet 4.5",
|
||||
"provider": "anthropic",
|
||||
"description": "Fastest Claude model for pure speed and simple tasks."
|
||||
"description": "Best combination of speed and intelligence with extended thinking."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -110,9 +110,9 @@
|
||||
},
|
||||
{
|
||||
"modelId": "deepseek-chat",
|
||||
"displayName": "DeepSeek V3",
|
||||
"displayName": "DeepSeek V3.1",
|
||||
"provider": "deepseek",
|
||||
"description": "General purpose chat model, specialized in code and reasoning."
|
||||
"description": "Latest 671B MoE with hybrid thinking/non-thinking modes, 128K context."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -120,15 +120,15 @@
|
||||
"key": "gemini",
|
||||
"displayName": "Google Gemini",
|
||||
"baseUrl": "https://generativelanguage.googleapis.com/",
|
||||
"endpoint": "v1beta/models/gemini-3-flash-preview:generateContent",
|
||||
"endpoint": "v1beta/models/gemini-2.5-pro:generateContent",
|
||||
"websiteUrl": "https://ai.google/",
|
||||
"isCustom": false,
|
||||
"models": [
|
||||
{
|
||||
"modelId": "gemini-3-flash-preview",
|
||||
"displayName": "Gemini 3 Flash",
|
||||
"modelId": "gemini-2.5-pro",
|
||||
"displayName": "Gemini 2.5 Pro",
|
||||
"provider": "gemini",
|
||||
"description": "Current default: Massive context, grounded, and extremely fast."
|
||||
"description": "Stable release: State-of-the-art reasoning with 1M context."
|
||||
},
|
||||
{
|
||||
"modelId": "gemini-3-pro-preview",
|
||||
@@ -156,16 +156,10 @@
|
||||
"isCustom": false,
|
||||
"models": [
|
||||
{
|
||||
"modelId": "llama-4-scout-17b",
|
||||
"displayName": "Llama 4 Scout",
|
||||
"modelId": "meta-llama/llama-4-maverick",
|
||||
"displayName": "Llama 4 Maverick",
|
||||
"provider": "groq",
|
||||
"description": "Powerful Llama 4 model running at extreme speed."
|
||||
},
|
||||
{
|
||||
"modelId": "llama-3.3-70b-versatile",
|
||||
"displayName": "Llama 3.3 70B",
|
||||
"provider": "groq",
|
||||
"description": "Previous gen flagship, highly reliable and fast on Groq chips."
|
||||
"description": "400B MoE powerhouse with industry-leading image and text understanding."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -216,10 +210,10 @@
|
||||
"description": "World's fastest inference (2000+ tokens/sec) on Wafer-Scale Engines."
|
||||
},
|
||||
{
|
||||
"modelId": "llama3.1-8b",
|
||||
"displayName": "Llama 3.1 8B",
|
||||
"modelId": "llama-4-scout",
|
||||
"displayName": "Llama 4 Scout",
|
||||
"provider": "cerebras",
|
||||
"description": "Instant speed for simple tasks."
|
||||
"description": "High-quality 17B active param model running at 2,600 tokens/sec."
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -238,10 +232,10 @@
|
||||
"description": "Hosted via the Hugging Face serverless router (Free tier limits apply)."
|
||||
},
|
||||
{
|
||||
"modelId": "microsoft/Phi-3.5-mini-instruct",
|
||||
"displayName": "Phi 3.5 Mini",
|
||||
"modelId": "Qwen/Qwen2.5-72B-Instruct",
|
||||
"displayName": "Qwen 2.5 72B",
|
||||
"provider": "huggingface",
|
||||
"description": "Highly capable small model from Microsoft."
|
||||
"description": "High-quality open model with excellent reasoning and multilingual capabilities."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,11 +5,9 @@ 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.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -42,82 +40,89 @@ import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.PrimaryButton
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
|
||||
@Composable
|
||||
fun IntroNavHost(onIntroFinished: () -> Unit) {
|
||||
val pages = listOf(
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_welcome),
|
||||
description = stringResource(R.string.intro_desc_welcome),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_welcome) }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_ai_assistant),
|
||||
description = stringResource(R.string.intro_desc_ai_assistant),
|
||||
content = {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
IconContent(iconRes = R.drawable.ic_intro_ai_agents)
|
||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally), verticalArrangement = Arrangement.spacedBy(2.dp), modifier = Modifier.fillMaxWidth()) {
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_mistral)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_your_own_ai)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_openai)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_claude)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_gemini)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_deepseek)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_openrouter)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_and_many_more)) })
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_welcome),
|
||||
description = stringResource(R.string.intro_desc_welcome),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_welcome) }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_ai_assistant),
|
||||
description = stringResource(R.string.intro_desc_ai_assistant),
|
||||
content = {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
IconContent(iconRes = R.drawable.ic_intro_ai_agents)
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_mistral)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_your_own_ai)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_openai)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_claude)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_gemini)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_deepseek)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_openrouter)) })
|
||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_and_many_more)) })
|
||||
}
|
||||
}
|
||||
}
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_dictionary_translator),
|
||||
description = stringResource(R.string.intro_desc_dictionary_translator),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_lookup) }
|
||||
}
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_dictionary_translator),
|
||||
description = stringResource(R.string.intro_desc_dictionary_translator),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_lookup) }
|
||||
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_flashcards),
|
||||
description = stringResource(R.string.intro_desc_flashcards),
|
||||
content = { FlashcardTopicsPreview() }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_practice),
|
||||
description = stringResource(R.string.intro_desc_practice),
|
||||
content = { IconContent(iconRes = R.drawable.ic_inro_practice) }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_learning_journey),
|
||||
description = stringResource(R.string.intro_desc_learning_journey),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_learning_journey)}
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_categories),
|
||||
description = stringResource(R.string.intro_desc_categories),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_categories) }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_progress),
|
||||
description = stringResource(R.string.intro_desc_progress),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_track_progress) }
|
||||
),
|
||||
IntroPageData(
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_flashcards),
|
||||
description = stringResource(R.string.intro_desc_flashcards),
|
||||
content = { FlashcardTopicsPreview() }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_practice),
|
||||
description = stringResource(R.string.intro_desc_practice),
|
||||
content = { IconContent(iconRes = R.drawable.ic_inro_practice) }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_learning_journey),
|
||||
description = stringResource(R.string.intro_desc_learning_journey),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_learning_journey) }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_categories),
|
||||
description = stringResource(R.string.intro_desc_categories),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_categories) }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_progress),
|
||||
description = stringResource(R.string.intro_desc_progress),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_track_progress) }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_need_help),
|
||||
description = stringResource(R.string.intro_if_you_need_help_you),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_help) }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_beta),
|
||||
description = stringResource(R.string.intro_desc_beta),
|
||||
content = { IconContent(iconRes = R.drawable.ic_icon_construction) }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_all_set),
|
||||
description = stringResource(R.string.intro_desc_all_set),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_robot) }
|
||||
)
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_beta),
|
||||
description = stringResource(R.string.intro_desc_beta),
|
||||
content = { IconContent(iconRes = R.drawable.ic_icon_construction) }
|
||||
),
|
||||
IntroPageData(
|
||||
title = stringResource(R.string.intro_title_all_set),
|
||||
description = stringResource(R.string.intro_desc_all_set),
|
||||
content = { IconContent(iconRes = R.drawable.ic_intro_robot) }
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
val pagerState = rememberPagerState(pageCount = { pages.size })
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
@@ -128,7 +133,6 @@ fun IntroNavHost(onIntroFinished: () -> Unit) {
|
||||
.statusBarsPadding()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
) {
|
||||
// Full-width Skip intro button aligned to end but sized like primary (fillMaxWidth)
|
||||
eu.gaudian.translator.view.composable.SecondaryButton(
|
||||
onClick = { onIntroFinished() },
|
||||
text = stringResource(R.string.intro_skip),
|
||||
@@ -145,7 +149,9 @@ fun IntroNavHost(onIntroFinished: () -> Unit) {
|
||||
) {
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.weight(1f)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) { pageIndex ->
|
||||
IntroPage(pageData = pages[pageIndex])
|
||||
}
|
||||
@@ -170,7 +176,7 @@ fun IntroNavHost(onIntroFinished: () -> Unit) {
|
||||
}
|
||||
},
|
||||
text = if (pagerState.currentPage < pages.size - 1) stringResource(R.string.next) else stringResource(R.string.get_started),
|
||||
icon = if (pagerState.currentPage < pages.size - 1)AppIcons.ArrowForwardNoChevron else null,
|
||||
icon = if (pagerState.currentPage < pages.size - 1) AppIcons.ArrowForwardNoChevron else null,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
@@ -189,9 +195,9 @@ private fun IntroPage(pageData: IntroPageData) {
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.CenterVertically),
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.fillMaxSize() // Fixed: This was previously fillMaxHeight()
|
||||
.padding(horizontal = 16.dp)
|
||||
.verticalScroll(rememberScrollState()) // Allow scrolling for larger hint content
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
@@ -234,15 +240,14 @@ private fun PagerIndicator(pageCount: Int, currentPage: Int) {
|
||||
@Composable
|
||||
private fun IconContent(iconRes: Int) {
|
||||
Box(modifier = Modifier.clip(RoundedCornerShape(16.dp))) {
|
||||
Icon(
|
||||
painter = painterResource(id = iconRes),
|
||||
contentDescription = null,
|
||||
tint = Color.Unspecified,
|
||||
modifier = Modifier.size(250.dp)
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(id = iconRes),
|
||||
contentDescription = null,
|
||||
tint = Color.Unspecified,
|
||||
modifier = Modifier.size(250.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun FlashcardTopicsPreview() {
|
||||
|
||||
@@ -41,6 +41,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.view.hints.Hint
|
||||
import eu.gaudian.translator.view.hints.HintBottomSheet
|
||||
import eu.gaudian.translator.view.hints.LocalShowHints
|
||||
|
||||
@@ -48,7 +49,7 @@ import eu.gaudian.translator.view.hints.LocalShowHints
|
||||
fun AppDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
title: (@Composable () -> Unit)? = null,
|
||||
hintContent: @Composable (() -> Unit)? = null,
|
||||
hintContent: Hint? = null,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
// 1. Swipe Resistance: Prevent accidental dismissal
|
||||
@@ -98,7 +99,7 @@ fun AppDialog(
|
||||
if (showBottomSheet) {
|
||||
EnhancedHintBottomSheet(
|
||||
onDismissRequest = { showBottomSheet = false },
|
||||
content = hintContent,
|
||||
content = {hintContent?.Render()},
|
||||
parentTitle = title
|
||||
)
|
||||
}
|
||||
@@ -156,7 +157,7 @@ fun AppAlertDialog(
|
||||
@Composable
|
||||
private fun DialogHeader(
|
||||
title: (@Composable () -> Unit)?,
|
||||
hintContent: @Composable (() -> Unit)?,
|
||||
hintContent: Hint? = null,
|
||||
onHintClick: () -> Unit,
|
||||
onCloseClick: () -> Unit
|
||||
) {
|
||||
@@ -327,7 +328,6 @@ fun AppDialogPreview() {
|
||||
AppDialog(
|
||||
onDismissRequest = {},
|
||||
title = { Text("Dialog Title") },
|
||||
hintContent = { Text("This is a hint.") },
|
||||
content = {
|
||||
Column {
|
||||
Text("Content line 1")
|
||||
@@ -378,7 +378,6 @@ fun AppDialogLongContentPreview() {
|
||||
AppDialog(
|
||||
onDismissRequest = {},
|
||||
title = { Text("Long Content Dialog") },
|
||||
hintContent = { Text("Hint for long content dialog") },
|
||||
content = {
|
||||
Column {
|
||||
Text("This is a long content dialog to test scrolling")
|
||||
|
||||
@@ -48,7 +48,7 @@ import eu.gaudian.translator.view.composable.AppDialog
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
import eu.gaudian.translator.view.composable.AppOutlinedTextField
|
||||
import eu.gaudian.translator.view.composable.MultipleLanguageDropdown
|
||||
import eu.gaudian.translator.view.hints.CategoryHint
|
||||
import eu.gaudian.translator.view.hints.HintDefinition
|
||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
|
||||
@@ -80,7 +80,7 @@ fun AddCategoryDialog(
|
||||
AppDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(stringResource(R.string.label_add_category)) },
|
||||
hintContent = { CategoryHint() },
|
||||
hintContent = HintDefinition.CATEGORY.hint(),
|
||||
content = {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
|
||||
|
||||
@@ -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.ImportVocabularyHint
|
||||
import eu.gaudian.translator.view.hints.HintDefinition
|
||||
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 = { ImportVocabularyHint() },
|
||||
hintContent = HintDefinition.IMPORT.hint(),
|
||||
content = {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
||||
@@ -34,7 +34,7 @@ import eu.gaudian.translator.view.composable.AppButton
|
||||
import eu.gaudian.translator.view.composable.AppCheckbox
|
||||
import eu.gaudian.translator.view.composable.AppScaffold
|
||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||
import eu.gaudian.translator.view.hints.getVocabularyReviewHint
|
||||
import eu.gaudian.translator.view.hints.HintDefinition
|
||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
|
||||
@@ -66,7 +66,7 @@ fun VocabularyReviewScreen(
|
||||
topBar = {
|
||||
AppTopAppBar(
|
||||
title = { Text(stringResource(R.string.found_items)) },
|
||||
hintContent = getVocabularyReviewHint()
|
||||
hintContent = HintDefinition.REVIEW.hint()
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.gaudian.translator.R
|
||||
|
||||
/**
|
||||
* 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.LocalizedMarkdown("example_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AddModelScanHintPreview() {
|
||||
getAddModelScanHint().Render()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AddModelScanHint() {
|
||||
getAddModelScanHint().Render()
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
@file:Suppress("HardCodedStringLiteral")
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* Hint metadata mapping Markdown filenames to their string resource titles.
|
||||
* All hint-related operations are available as functions on each enum entry.
|
||||
*/
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
enum class HintDefinition(
|
||||
val markdownFile: String,
|
||||
val titleRes: Int
|
||||
) {
|
||||
ADD_MODEL_SCAN("find_ai_model", R.string.hint_scan_hint_title),
|
||||
API_KEY("api_key_hint", R.string.hint_how_to_connect_to_an_ai),
|
||||
CATEGORY("category_hint", R.string.category_hint_intro),
|
||||
DICTIONARY_OPTIONS("dictionary_hint", R.string.label_dictionary_options),
|
||||
EXERCISE("exercise_hint", R.string.label_exercise),
|
||||
IMPORT("import_hint", R.string.hint_how_to_generate_vocabulary_with_ai),
|
||||
LEARNING_STAGES("learning_stages_hint", R.string.learning_stages_title),
|
||||
REVIEW("review_hint", R.string.review_intro),
|
||||
SORTING("sorting_hint", R.string.sorting_hint_title),
|
||||
TRANSLATION("translation_hint", R.string.hint_translate_how_it_works),
|
||||
VOCABULARY_PROGRESS("vocabulary_progress_hint", R.string.hint_vocabulary_progress_hint_title);
|
||||
|
||||
/** Creates the Hint data class for this hint definition. */
|
||||
@Composable
|
||||
fun hint() = Hint(titleRes = titleRes, elements = listOf(HintElement.LocalizedMarkdown(markdownFile)))
|
||||
|
||||
/** Renders this hint's content. */
|
||||
@Composable
|
||||
fun Render() = hint().Render()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun hint(definition: HintDefinition): Hint = definition.hint()
|
||||
|
||||
@Composable fun HintContent(definition: HintDefinition) = definition.Render()
|
||||
@Composable fun HintScreen(navController: NavController, definition: HintDefinition) = HintScreen(
|
||||
navController = navController,
|
||||
title = stringResource(definition.titleRes),
|
||||
content = { definition.Render() }
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.gaudian.translator.R
|
||||
|
||||
/**
|
||||
* Migrated API Key hint using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
@Composable
|
||||
fun getApiKeyHint() = Hint (
|
||||
titleRes = R.string.hint_how_to_connect_to_an_ai,
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("api_key_hint")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@Composable
|
||||
fun ApiKeyHint() {
|
||||
getApiKeyHint().Render()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ApiKeyHintPreview() {
|
||||
MaterialTheme {
|
||||
ApiKeyHint()
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.gaudian.translator.R
|
||||
|
||||
/**
|
||||
* Migrated Category hint using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
@Composable
|
||||
fun getCategoryHint(): Hint {
|
||||
return Hint(
|
||||
titleRes = R.string.category_hint_intro,
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("category_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CategoryHint() {
|
||||
getCategoryHint().Render()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun CategoryHintPreview() {
|
||||
MaterialTheme {
|
||||
CategoryHint()
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.gaudian.translator.R
|
||||
|
||||
/**
|
||||
* Migrated Category Hint Screen using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
@Composable
|
||||
fun getCategoryHintScreen(): Hint {
|
||||
return Hint(
|
||||
titleRes = R.string.category_hint_intro,
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("category_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CategoryHintScreen() {
|
||||
getCategoryHintScreen().Render()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun CategoryHintScreenPreview() {
|
||||
MaterialTheme {
|
||||
CategoryHintScreen()
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.gaudian.translator.R
|
||||
|
||||
/**
|
||||
* 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.LocalizedMarkdown("dictionary_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun DictionaryOptionsHint() {
|
||||
getDictionaryOptionsHint().Render()
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.gaudian.translator.R
|
||||
|
||||
/**
|
||||
* Example of a migrated hint using the new markdown-based approach.
|
||||
*
|
||||
* This demonstrates how to migrate from the old LegacyHint format to the new
|
||||
* markdown-based format.
|
||||
*
|
||||
* Benefits:
|
||||
* - Easier to manage and translate (no code changes needed)
|
||||
* - Better separation of concerns
|
||||
* - Consistent styling across all hints
|
||||
* - Support for rich formatting (tables, code blocks, etc.)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Example 1: Loading markdown content from string (simple).
|
||||
*/
|
||||
@Composable
|
||||
fun getApiKeyMarkdownHint(): String {
|
||||
return """
|
||||
# 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.
|
||||
|
||||
> **Note:** Keep your API key secure and never share it publicly.
|
||||
|
||||
## Key Status Indicators
|
||||
|
||||
| Status | Icon | Meaning |
|
||||
|--------|------|---------|
|
||||
| Active | ✅ | Key is valid and working |
|
||||
| Missing | ⚠️ | Key is not set |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
1. Verify the key is correct
|
||||
2. Ensure proper permissions
|
||||
3. Check your quota
|
||||
|
||||
```json
|
||||
{
|
||||
"api_key": "sk-xxxxxxxxxxxx"
|
||||
}
|
||||
```
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ApiKeyMarkdownHint() {
|
||||
val content = getApiKeyMarkdownHint()
|
||||
MarkdownHint(
|
||||
markdownContent = content,
|
||||
title = stringResource(R.string.hint_how_to_connect_to_an_ai)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 2: Loading markdown from assets file (recommended for production).
|
||||
*/
|
||||
@Composable
|
||||
fun loadMarkdownFromAssets(fileName: String): String {
|
||||
val context = LocalContext.current
|
||||
return try {
|
||||
context.assets.open("hints/$fileName").bufferedReader().use { it.readText() }
|
||||
} catch (e: Exception) {
|
||||
"Error loading markdown file: ${e.message}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class for programmatic hint loading.
|
||||
*/
|
||||
data class MarkdownHintDefinition(
|
||||
val fileName: String,
|
||||
val titleRes: Int
|
||||
) {
|
||||
fun loadContent(context: Context): String {
|
||||
return context.assets.open("hints/$fileName").bufferedReader().use { it.readText() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-defined hints ready for migration.
|
||||
*/
|
||||
object MarkdownHints {
|
||||
val API_KEY = MarkdownHintDefinition(
|
||||
fileName = "api_key_hint.md",
|
||||
titleRes = R.string.hint_how_to_connect_to_an_ai
|
||||
)
|
||||
val CATEGORY = MarkdownHintDefinition(
|
||||
fileName = "category_hint.md",
|
||||
titleRes = R.string.category_hint_intro
|
||||
)
|
||||
val LEARNING_STAGES = MarkdownHintDefinition(
|
||||
fileName = "learning_stages_hint.md",
|
||||
titleRes = R.string.learning_stages_title
|
||||
)
|
||||
val SORTING = MarkdownHintDefinition(
|
||||
fileName = "sorting_hint.md",
|
||||
titleRes = R.string.sorting_hint_title
|
||||
)
|
||||
val VOCABULARY_PROGRESS = MarkdownHintDefinition(
|
||||
fileName = "vocabulary_progress_hint.md",
|
||||
titleRes = R.string.hint_vocabulary_progress_hint_title
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview for the migrated API Key hint.
|
||||
*/
|
||||
@Preview
|
||||
@Composable
|
||||
fun ApiKeyMarkdownHintPreview() {
|
||||
MaterialTheme {
|
||||
ApiKeyMarkdownHint()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview for loading from assets.
|
||||
*/
|
||||
@Preview
|
||||
@Composable
|
||||
fun LoadFromAssetsPreview() {
|
||||
MaterialTheme {
|
||||
val content = loadMarkdownFromAssets("example_hint.md")
|
||||
MarkdownHint(
|
||||
markdownContent = content,
|
||||
title = stringResource(R.string.hint_title_hints_overview)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
/**
|
||||
* This file is kept for reference only.
|
||||
* All hints are now migrated to markdown-based format.
|
||||
* See individual hint files for implementations.
|
||||
*/
|
||||
@@ -1,3 +1,5 @@
|
||||
@file:Suppress("HardCodedStringLiteral")
|
||||
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -9,9 +11,9 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.jeziellago.compose.markdowntext.MarkdownText
|
||||
import eu.gaudian.translator.utils.Log
|
||||
|
||||
private const val TAG = "MarkdownHint"
|
||||
|
||||
@@ -25,7 +27,7 @@ sealed class HintElement {
|
||||
data class UIElement(val composable: @Composable () -> Unit) : HintElement()
|
||||
|
||||
/**
|
||||
* A localized markdown file element.
|
||||
* 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)
|
||||
@@ -56,7 +58,7 @@ fun RenderHintElement(element: HintElement) {
|
||||
androidx.compose.foundation.layout.Row(
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
Text(
|
||||
text = "[DEBUG: ${element.fileName}]",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
@@ -68,7 +70,7 @@ fun RenderHintElement(element: HintElement) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Composable to render localized markdown content.
|
||||
* 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.
|
||||
*/
|
||||
@@ -85,23 +87,23 @@ fun LocalizedMarkdownContent(
|
||||
// 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")
|
||||
Log.d(TAG, "Loading hint: $fileName")
|
||||
Log.d(TAG, "Device locale: ${locale.language}_${locale.country}")
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
Log.d(TAG, "Found default version at: $defaultPath")
|
||||
} else {
|
||||
android.util.Log.e(TAG, "No hint found for: $fileName (tried: $localizedPath, $defaultPath)")
|
||||
Log.e(TAG, "No hint found for: $fileName (tried: $localizedPath, $defaultPath)")
|
||||
}
|
||||
default
|
||||
}
|
||||
@@ -125,22 +127,3 @@ fun LocalizedMarkdownContent(
|
||||
}
|
||||
|
||||
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
@Preview
|
||||
@Composable
|
||||
fun UIElementPreview() {
|
||||
RenderHintElement(
|
||||
HintElement.UIElement {
|
||||
Text("Custom UI Element")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
@Preview
|
||||
@Composable
|
||||
fun LocalizedMarkdownElementPreview() {
|
||||
RenderHintElement(
|
||||
HintElement.LocalizedMarkdown("example_hint")
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
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
|
||||
*/
|
||||
@Composable
|
||||
fun CategoryHintScreenWrapper(navController: NavController) {
|
||||
HintScreen(
|
||||
navController = navController,
|
||||
title = stringResource(R.string.category_hint_intro)
|
||||
) {
|
||||
CategoryHint()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dictionary Hint Screen
|
||||
*/
|
||||
@Composable
|
||||
fun DictionaryHintScreen(navController: NavController) {
|
||||
HintScreen(
|
||||
navController = navController,
|
||||
title = stringResource(R.string.label_dictionary_options)
|
||||
) {
|
||||
getDictionaryOptionsHint().Render()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import Hint Screen
|
||||
*/
|
||||
@Composable
|
||||
fun ImportHintScreen(navController: NavController) {
|
||||
HintScreen(
|
||||
navController = navController,
|
||||
title = stringResource(R.string.hint_how_to_generate_vocabulary_with_ai)
|
||||
) {
|
||||
getImportVocabularyHint()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorting Hint Screen
|
||||
*/
|
||||
@Composable
|
||||
fun SortingHintScreen(navController: NavController) {
|
||||
HintScreen(
|
||||
navController = navController,
|
||||
title = stringResource(R.string.sorting_hint_title)
|
||||
) {
|
||||
SortingScreenHint()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stages Hint Screen
|
||||
*/
|
||||
@Composable
|
||||
fun StagesHintScreen(navController: NavController) {
|
||||
HintScreen(
|
||||
navController = navController,
|
||||
title = stringResource(R.string.learning_stages_title)
|
||||
) {
|
||||
LearningStagesHint()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translation Hint Screen
|
||||
*/
|
||||
@Composable
|
||||
fun TranslationHintScreen(navController: NavController) {
|
||||
HintScreen(
|
||||
navController = navController,
|
||||
title = stringResource(R.string.hint_translate_how_it_works)
|
||||
) {
|
||||
getTranslationScreenHint().Render()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan Hint Screen
|
||||
*/
|
||||
@Composable
|
||||
fun ScanHintScreen(navController: NavController) {
|
||||
HintScreen(
|
||||
navController = navController,
|
||||
title = stringResource(R.string.hint_scan_hint_title)
|
||||
) {
|
||||
AddModelScanHint()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* API Hint Screen
|
||||
*/
|
||||
@Composable
|
||||
fun ApiHintScreen(navController: NavController) {
|
||||
HintScreen(
|
||||
navController = navController,
|
||||
title = stringResource(R.string.hint_how_to_connect_to_an_ai)
|
||||
) {
|
||||
ApiKeyHint()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vocabulary Progress Hint Screen
|
||||
*/
|
||||
@Composable
|
||||
fun VocabularyProgressHintScreen(navController: NavController) {
|
||||
HintScreen(
|
||||
navController = navController,
|
||||
title = stringResource(R.string.hint_vocabulary_progress_hint_title)
|
||||
) {
|
||||
VocabularyProgressHint()
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import eu.gaudian.translator.R
|
||||
import eu.gaudian.translator.ui.theme.ThemePreviews
|
||||
import eu.gaudian.translator.view.LocalShowExperimentalFeatures
|
||||
import eu.gaudian.translator.view.composable.AppCard
|
||||
import eu.gaudian.translator.view.composable.AppIcons
|
||||
@@ -42,15 +40,16 @@ 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 importHint = HintDefinition.IMPORT.hint()
|
||||
val addModelScanHint = HintDefinition.ADD_MODEL_SCAN.hint()
|
||||
val dictionaryOptionsHint = HintDefinition.DICTIONARY_OPTIONS.hint()
|
||||
val translationScreenHint = HintDefinition.TRANSLATION.hint()
|
||||
val categoryHint = HintDefinition.CATEGORY.hint()
|
||||
val learningStagesHint = HintDefinition.LEARNING_STAGES.hint()
|
||||
val sortingScreenHint = HintDefinition.SORTING.hint()
|
||||
val vocabularyProgressHint = HintDefinition.VOCABULARY_PROGRESS.hint()
|
||||
val apiKeyHint = HintDefinition.API_KEY.hint()
|
||||
|
||||
|
||||
val hintGroups = remember(showExperimental, importHint, addModelScanHint, dictionaryOptionsHint, translationScreenHint) {
|
||||
val allGroups = listOf(
|
||||
@@ -133,11 +132,7 @@ fun HintsOverviewScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
fun HintsOverviewScreenPreview() {
|
||||
HintsOverviewScreen(navController = rememberNavController())
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun HintHeader(
|
||||
@@ -176,12 +171,4 @@ private fun HintListItem(
|
||||
)
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
fun HintListItemPreview() {
|
||||
HintListItem(
|
||||
title = stringResource(R.string.category_hint_intro),
|
||||
icon = AppIcons.Category,
|
||||
onClick = {}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.gaudian.translator.R
|
||||
|
||||
/**
|
||||
* 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.LocalizedMarkdown("import_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun ImportVocabularyHint() {
|
||||
getImportVocabularyHint().Render()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
HintElement.LocalizedMarkdown("review_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun VocabularyReviewHint() {
|
||||
getVocabularyReviewHint()
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.gaudian.translator.R
|
||||
|
||||
/**
|
||||
* Migrated Learning Stages hint using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
@Composable
|
||||
fun getLearningStagesHint(): Hint {
|
||||
return Hint(
|
||||
titleRes = R.string.learning_stages_title,
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("learning_stages_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LearningStagesHint() {
|
||||
getLearningStagesHint().Render()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun LearningStagesHintPreview() {
|
||||
MaterialTheme {
|
||||
LearningStagesHint()
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
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 dev.jeziellago.compose.markdowntext.MarkdownText
|
||||
import eu.gaudian.translator.R
|
||||
|
||||
/**
|
||||
* Markdown-styled hint content using the jeziellago compose-markdown library.
|
||||
* This provides beautiful, consistent rendering of markdown content with
|
||||
* support for headings, lists, tables, code blocks, and more.
|
||||
*
|
||||
* Usage:
|
||||
* - Create a .md file in assets/hints/ (e.g., "api_key_hint.md")
|
||||
* - Load it using context.assets.open() or pass raw markdown string
|
||||
* - Use MarkdownHint composable for styled rendering
|
||||
*
|
||||
* Supported markdown:
|
||||
* - Headings (# ## ###)
|
||||
* - Bold (**text**) and italic (*text*)
|
||||
* - Lists (- item, 1. item)
|
||||
* - Tables (| col | col |)
|
||||
* - Code blocks (```code```)
|
||||
* - Blockquotes (> quote)
|
||||
* - Links ([text](url))
|
||||
*/
|
||||
@Composable
|
||||
fun MarkdownHint(
|
||||
markdownContent: String,
|
||||
modifier: Modifier = Modifier,
|
||||
title: String? = null
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(scrollState)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// Optional title with icon header
|
||||
title?.let {
|
||||
HeaderWithIcon(
|
||||
title = it,
|
||||
subtitle = null
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
// Render markdown
|
||||
MarkdownText(
|
||||
markdown = markdownContent,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
style = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview for MarkdownHint with sample content.
|
||||
*/
|
||||
@Preview
|
||||
@Composable
|
||||
fun MarkdownHintPreview() {
|
||||
val sampleMarkdown = """
|
||||
# Welcome to the Hint System
|
||||
|
||||
This is a **markdown-based** hint that provides _beautiful_ and consistent styling.
|
||||
|
||||
## Features
|
||||
|
||||
- **Rich text formatting** - bold, italic, strikethrough
|
||||
- **Headings** - H1 through H6 levels
|
||||
- **Lists** - ordered and unordered
|
||||
- **Code blocks** - with syntax highlighting
|
||||
- **Tables** - for structured data
|
||||
- **Links** - styled clickable references
|
||||
- **Blockquotes** - for highlighted content
|
||||
|
||||
## How to Use
|
||||
|
||||
1. Create a `.md` file in `assets/hints/`
|
||||
2. Load it using `context.assets.open("hints/your_hint.md")`
|
||||
3. Pass the content to `MarkdownHint()`
|
||||
|
||||
> **Tip:** You can also embed UI elements by combining markdown with HintElements!
|
||||
|
||||
## Code Example
|
||||
|
||||
```kotlin
|
||||
val markdown = loadMarkdownFromAssets("hints/scan_hint.md")
|
||||
MarkdownHint(
|
||||
markdownContent = markdown,
|
||||
title = "Scan Hint"
|
||||
)
|
||||
```
|
||||
|
||||
## Table Example
|
||||
|
||||
| Feature | Status | Priority |
|
||||
|---------|--------|----------|
|
||||
| Headings | ✅ | High |
|
||||
| Lists | ✅ | High |
|
||||
| Tables | ✅ | Medium |
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2024-01-15*
|
||||
""".trimIndent()
|
||||
|
||||
MaterialTheme {
|
||||
MarkdownHint(
|
||||
markdownContent = sampleMarkdown,
|
||||
title = stringResource(R.string.hint_title_hints_overview)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data class for loading markdown content from assets.
|
||||
*/
|
||||
data class MarkdownHintData(
|
||||
val fileName: String,
|
||||
val titleRes: Int
|
||||
) {
|
||||
/**
|
||||
* Load and return the markdown content as a string.
|
||||
*/
|
||||
fun loadContent(androidContext: android.content.Context): String {
|
||||
return androidContext.assets.open("hints/$fileName")
|
||||
.bufferedReader()
|
||||
.use { it.readText() }
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import android.content.Context
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Internationalization system for markdown hints.
|
||||
* Internationalization system for Markdown hints.
|
||||
*
|
||||
* This follows Android's locale-qualified resource pattern:
|
||||
* - assets/hints/ - Default (English)
|
||||
@@ -18,131 +18,22 @@ import java.util.Locale
|
||||
*/
|
||||
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"
|
||||
}
|
||||
|
||||
|
||||
fun getCurrentLocale(context: Context): Locale {
|
||||
return 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) {
|
||||
} catch (_: 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.
|
||||
*/
|
||||
@@ -163,55 +54,3 @@ object MarkdownHintLoader {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,33 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.gaudian.translator.R
|
||||
|
||||
/**
|
||||
* Migrated Sorting Screen hint using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
@Composable
|
||||
fun getSortingScreenHint(): Hint {
|
||||
return Hint(
|
||||
titleRes = R.string.sorting_hint_title,
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("sorting_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SortingScreenHint() {
|
||||
getSortingScreenHint()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SortingScreenHintPreview() {
|
||||
MaterialTheme {
|
||||
SortingScreenHint()
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.gaudian.translator.R
|
||||
|
||||
/**
|
||||
* 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.LocalizedMarkdown("translation_hint")
|
||||
)
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun TranslationScreenHint() {
|
||||
getTranslationScreenHint().Render()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun TranslationScreenHintPreview() {
|
||||
getTranslationScreenHint().Render()
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package eu.gaudian.translator.view.hints
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import eu.gaudian.translator.R
|
||||
|
||||
/**
|
||||
* Migrated Vocabulary Progress hint using markdown-based format.
|
||||
* Content is loaded from localized assets/hints based on device locale.
|
||||
*/
|
||||
@Composable
|
||||
fun getVocabularyProgressHint(): Hint {
|
||||
return Hint(
|
||||
titleRes = R.string.hint_vocabulary_progress_hint_title,
|
||||
elements = listOf(
|
||||
HintElement.LocalizedMarkdown("vocabulary_progress_hint")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun VocabularyProgressHint() {
|
||||
getVocabularyProgressHint().Render()
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun VocabularyProgressHintPreview() {
|
||||
MaterialTheme {
|
||||
VocabularyProgressHint()
|
||||
}
|
||||
}
|
||||
@@ -58,8 +58,7 @@ import eu.gaudian.translator.view.composable.AppScaffold
|
||||
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.view.hints.HintDefinition
|
||||
import eu.gaudian.translator.viewmodel.ApiViewModel
|
||||
|
||||
@Composable
|
||||
@@ -141,7 +140,7 @@ fun AddModelScreen(navController: NavController, providerKey: String) {
|
||||
Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back))
|
||||
}
|
||||
},
|
||||
hintContent = getAddModelScanHint()
|
||||
hintContent = HintDefinition.ADD_MODEL_SCAN.hint()
|
||||
)
|
||||
},
|
||||
) { 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.getApiKeyHint
|
||||
import eu.gaudian.translator.view.hints.HintDefinition
|
||||
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 = getApiKeyHint()
|
||||
hintContent = HintDefinition.API_KEY.hint()
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
|
||||
@@ -36,7 +36,7 @@ import eu.gaudian.translator.view.composable.AppScaffold
|
||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||
import eu.gaudian.translator.view.composable.OptionItemSwitch
|
||||
import eu.gaudian.translator.view.dictionary.DictionaryManagerContent
|
||||
import eu.gaudian.translator.view.hints.getDictionaryOptionsHint
|
||||
import eu.gaudian.translator.view.hints.HintDefinition
|
||||
import eu.gaudian.translator.viewmodel.ApiViewModel
|
||||
import eu.gaudian.translator.viewmodel.DictionaryViewModel
|
||||
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
||||
@@ -72,7 +72,7 @@ fun DictionaryOptionsScreen(
|
||||
Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back))
|
||||
}
|
||||
},
|
||||
hintContent = getDictionaryOptionsHint()
|
||||
hintContent = HintDefinition.DICTIONARY_OPTIONS.hint()
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
|
||||
@@ -7,16 +7,9 @@ import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navigation
|
||||
import eu.gaudian.translator.view.composable.Screen
|
||||
import eu.gaudian.translator.view.hints.ApiHintScreen
|
||||
import eu.gaudian.translator.view.hints.CategoryHintScreenWrapper
|
||||
import eu.gaudian.translator.view.hints.DictionaryHintScreen
|
||||
import eu.gaudian.translator.view.hints.HintDefinition
|
||||
import eu.gaudian.translator.view.hints.HintScreen
|
||||
import eu.gaudian.translator.view.hints.HintsOverviewScreen
|
||||
import eu.gaudian.translator.view.hints.ImportHintScreen
|
||||
import eu.gaudian.translator.view.hints.ScanHintScreen
|
||||
import eu.gaudian.translator.view.hints.SortingHintScreen
|
||||
import eu.gaudian.translator.view.hints.StagesHintScreen
|
||||
import eu.gaudian.translator.view.hints.TranslationHintScreen
|
||||
import eu.gaudian.translator.view.hints.VocabularyProgressHintScreen
|
||||
|
||||
// Defines the routes for the settings graph to avoid using raw strings
|
||||
object SettingsRoutes {
|
||||
@@ -114,31 +107,31 @@ fun NavGraphBuilder.settingsGraph(navController: NavController) {
|
||||
HintsOverviewScreen(navController = navController)
|
||||
}
|
||||
composable(SettingsRoutes.HINTS_CATEGORIES) {
|
||||
CategoryHintScreenWrapper(navController = navController)
|
||||
HintScreen(navController, HintDefinition.CATEGORY)
|
||||
}
|
||||
composable(SettingsRoutes.HINTS_DICTIONARY) {
|
||||
DictionaryHintScreen(navController = navController)
|
||||
HintScreen(navController, HintDefinition.DICTIONARY_OPTIONS)
|
||||
}
|
||||
composable(SettingsRoutes.HINTS_IMPORT) {
|
||||
ImportHintScreen(navController = navController)
|
||||
HintScreen(navController, HintDefinition.IMPORT)
|
||||
}
|
||||
composable(SettingsRoutes.HINTS_SORTING) {
|
||||
SortingHintScreen(navController = navController)
|
||||
HintScreen(navController, HintDefinition.SORTING)
|
||||
}
|
||||
composable(SettingsRoutes.HINTS_STAGES) {
|
||||
StagesHintScreen(navController = navController)
|
||||
HintScreen(navController, HintDefinition.LEARNING_STAGES)
|
||||
}
|
||||
composable(SettingsRoutes.HINTS_TRANSLATION) {
|
||||
TranslationHintScreen(navController = navController)
|
||||
HintScreen(navController, HintDefinition.TRANSLATION)
|
||||
}
|
||||
composable(SettingsRoutes.HINTS_SCAN) {
|
||||
ScanHintScreen(navController = navController)
|
||||
HintScreen(navController, HintDefinition.ADD_MODEL_SCAN)
|
||||
}
|
||||
composable(SettingsRoutes.HINTS_API) {
|
||||
ApiHintScreen(navController = navController)
|
||||
HintScreen(navController, HintDefinition.API_KEY)
|
||||
}
|
||||
composable(SettingsRoutes.HINTS_VOCABULARY_PROGRESS) {
|
||||
VocabularyProgressHintScreen(navController = navController)
|
||||
HintScreen(navController, HintDefinition.VOCABULARY_PROGRESS)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,8 +49,7 @@ import eu.gaudian.translator.view.composable.AppIcons
|
||||
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.view.hints.HintDefinition
|
||||
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||
import kotlin.math.exp
|
||||
@@ -86,7 +85,7 @@ fun VocabularyProgressOptionsScreen(
|
||||
}
|
||||
},
|
||||
// Here is the new hint content
|
||||
hintContent = getVocabularyProgressHint()
|
||||
hintContent = HintDefinition.VOCABULARY_PROGRESS.hint()
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
|
||||
@@ -59,7 +59,7 @@ import eu.gaudian.translator.view.NoConnectionScreen
|
||||
import eu.gaudian.translator.view.composable.AppCard
|
||||
import eu.gaudian.translator.view.composable.AppOutlinedCard
|
||||
import eu.gaudian.translator.view.dialogs.AddVocabularyDialog
|
||||
import eu.gaudian.translator.view.hints.TranslationScreenHint
|
||||
import eu.gaudian.translator.view.hints.HintDefinition
|
||||
import eu.gaudian.translator.view.settings.SettingsRoutes
|
||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
||||
@@ -167,7 +167,7 @@ private fun LoadedTranslationContent(
|
||||
TopBarActions(
|
||||
languageViewModel = languageViewModel,
|
||||
onSettingsClick = onSettingsClick,
|
||||
hintContent = { TranslationScreenHint() }
|
||||
hintContent = { HintDefinition.TRANSLATION.Render() }
|
||||
)
|
||||
|
||||
AppCard(modifier = Modifier.padding(8.dp, end = 8.dp, bottom = 8.dp, top = 0.dp)) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import androidx.compose.foundation.BorderStroke
|
||||
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
|
||||
@@ -391,7 +390,6 @@ private fun ExerciseTypeSelector(
|
||||
onTypeSelected: (VocabularyExerciseType) -> Unit,
|
||||
) {
|
||||
// Using FlowRow for a more flexible layout that wraps to the next line if needed
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
FlowRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally),
|
||||
|
||||
@@ -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.getSortingScreenHint
|
||||
import eu.gaudian.translator.view.hints.HintDefinition
|
||||
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 = getSortingScreenHint()
|
||||
hintContent = HintDefinition.SORTING.hint()
|
||||
)
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user