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>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<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">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\jonas\.android\avd\Medium_Phone_28.avd" />
|
<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."
|
"description": "Next-gen efficient architecture; outperforms older 70B models."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"modelId": "deepseek-ai/DeepSeek-V3",
|
"modelId": "deepseek-ai/DeepSeek-V3.1",
|
||||||
"displayName": "DeepSeek V3",
|
"displayName": "DeepSeek V3.1",
|
||||||
"provider": "together",
|
"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,
|
"isCustom": false,
|
||||||
"models": [
|
"models": [
|
||||||
{
|
{
|
||||||
"modelId": "ministral-8b-latest",
|
"modelId": "mistral-medium-latest",
|
||||||
"displayName": "Ministral 8B",
|
"displayName": "Mistral Medium",
|
||||||
"provider": "mistral",
|
"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",
|
"modelId": "mistral-large-latest",
|
||||||
@@ -58,17 +58,17 @@
|
|||||||
"websiteUrl": "https://platform.openai.com/",
|
"websiteUrl": "https://platform.openai.com/",
|
||||||
"isCustom": false,
|
"isCustom": false,
|
||||||
"models": [
|
"models": [
|
||||||
|
{
|
||||||
|
"modelId": "gpt-5.2",
|
||||||
|
"displayName": "GPT-5.2",
|
||||||
|
"provider": "openai",
|
||||||
|
"description": "Balanced performance with enhanced reasoning and creativity."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"modelId": "gpt-5.1-instant",
|
"modelId": "gpt-5.1-instant",
|
||||||
"displayName": "GPT-5.1 Instant",
|
"displayName": "GPT-5.1 Instant",
|
||||||
"provider": "openai",
|
"provider": "openai",
|
||||||
"description": "The standard high-speed efficiency model replacing older 'Nano' tiers."
|
"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,
|
"isCustom": false,
|
||||||
"models": [
|
"models": [
|
||||||
{
|
{
|
||||||
"modelId": "claude-sonnet-5-20260203",
|
"modelId": "claude-opus-4-6",
|
||||||
"displayName": "Claude Sonnet 5",
|
"displayName": "Claude Opus 4.6",
|
||||||
"provider": "anthropic",
|
"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",
|
"modelId": "claude-sonnet-4-5",
|
||||||
"displayName": "Claude 4.5 Haiku",
|
"displayName": "Claude Sonnet 4.5",
|
||||||
"provider": "anthropic",
|
"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",
|
"modelId": "deepseek-chat",
|
||||||
"displayName": "DeepSeek V3",
|
"displayName": "DeepSeek V3.1",
|
||||||
"provider": "deepseek",
|
"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",
|
"key": "gemini",
|
||||||
"displayName": "Google Gemini",
|
"displayName": "Google Gemini",
|
||||||
"baseUrl": "https://generativelanguage.googleapis.com/",
|
"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/",
|
"websiteUrl": "https://ai.google/",
|
||||||
"isCustom": false,
|
"isCustom": false,
|
||||||
"models": [
|
"models": [
|
||||||
{
|
{
|
||||||
"modelId": "gemini-3-flash-preview",
|
"modelId": "gemini-2.5-pro",
|
||||||
"displayName": "Gemini 3 Flash",
|
"displayName": "Gemini 2.5 Pro",
|
||||||
"provider": "gemini",
|
"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",
|
"modelId": "gemini-3-pro-preview",
|
||||||
@@ -156,16 +156,10 @@
|
|||||||
"isCustom": false,
|
"isCustom": false,
|
||||||
"models": [
|
"models": [
|
||||||
{
|
{
|
||||||
"modelId": "llama-4-scout-17b",
|
"modelId": "meta-llama/llama-4-maverick",
|
||||||
"displayName": "Llama 4 Scout",
|
"displayName": "Llama 4 Maverick",
|
||||||
"provider": "groq",
|
"provider": "groq",
|
||||||
"description": "Powerful Llama 4 model running at extreme speed."
|
"description": "400B MoE powerhouse with industry-leading image and text understanding."
|
||||||
},
|
|
||||||
{
|
|
||||||
"modelId": "llama-3.3-70b-versatile",
|
|
||||||
"displayName": "Llama 3.3 70B",
|
|
||||||
"provider": "groq",
|
|
||||||
"description": "Previous gen flagship, highly reliable and fast on Groq chips."
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -216,10 +210,10 @@
|
|||||||
"description": "World's fastest inference (2000+ tokens/sec) on Wafer-Scale Engines."
|
"description": "World's fastest inference (2000+ tokens/sec) on Wafer-Scale Engines."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"modelId": "llama3.1-8b",
|
"modelId": "llama-4-scout",
|
||||||
"displayName": "Llama 3.1 8B",
|
"displayName": "Llama 4 Scout",
|
||||||
"provider": "cerebras",
|
"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)."
|
"description": "Hosted via the Hugging Face serverless router (Free tier limits apply)."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"modelId": "microsoft/Phi-3.5-mini-instruct",
|
"modelId": "Qwen/Qwen2.5-72B-Instruct",
|
||||||
"displayName": "Phi 3.5 Mini",
|
"displayName": "Qwen 2.5 72B",
|
||||||
"provider": "huggingface",
|
"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.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -42,81 +40,88 @@ import eu.gaudian.translator.view.composable.AppIcons
|
|||||||
import eu.gaudian.translator.view.composable.PrimaryButton
|
import eu.gaudian.translator.view.composable.PrimaryButton
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun IntroNavHost(onIntroFinished: () -> Unit) {
|
fun IntroNavHost(onIntroFinished: () -> Unit) {
|
||||||
val pages = listOf(
|
val pages = listOf(
|
||||||
IntroPageData(
|
IntroPageData(
|
||||||
title = stringResource(R.string.intro_title_welcome),
|
title = stringResource(R.string.intro_title_welcome),
|
||||||
description = stringResource(R.string.intro_desc_welcome),
|
description = stringResource(R.string.intro_desc_welcome),
|
||||||
content = { IconContent(iconRes = R.drawable.ic_intro_welcome) }
|
content = { IconContent(iconRes = R.drawable.ic_intro_welcome) }
|
||||||
),
|
),
|
||||||
IntroPageData(
|
IntroPageData(
|
||||||
title = stringResource(R.string.intro_title_ai_assistant),
|
title = stringResource(R.string.intro_title_ai_assistant),
|
||||||
description = stringResource(R.string.intro_desc_ai_assistant),
|
description = stringResource(R.string.intro_desc_ai_assistant),
|
||||||
content = {
|
content = {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
Column(
|
||||||
IconContent(iconRes = R.drawable.ic_intro_ai_agents)
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
FlowRow(horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally), verticalArrangement = Arrangement.spacedBy(2.dp), modifier = Modifier.fillMaxWidth()) {
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_mistral)) })
|
) {
|
||||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_your_own_ai)) })
|
IconContent(iconRes = R.drawable.ic_intro_ai_agents)
|
||||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_openai)) })
|
FlowRow(
|
||||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_claude)) })
|
horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally),
|
||||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_gemini)) })
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_deepseek)) })
|
modifier = Modifier.fillMaxWidth()
|
||||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_openrouter)) })
|
) {
|
||||||
SuggestionChip(onClick = { }, label = { Text(stringResource(R.string.text_and_many_more)) })
|
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(
|
IntroPageData(
|
||||||
title = stringResource(R.string.intro_title_dictionary_translator),
|
title = stringResource(R.string.intro_title_dictionary_translator),
|
||||||
description = stringResource(R.string.intro_desc_dictionary_translator),
|
description = stringResource(R.string.intro_desc_dictionary_translator),
|
||||||
content = { IconContent(iconRes = R.drawable.ic_intro_lookup) }
|
content = { IconContent(iconRes = R.drawable.ic_intro_lookup) }
|
||||||
|
|
||||||
),
|
),
|
||||||
IntroPageData(
|
IntroPageData(
|
||||||
title = stringResource(R.string.intro_title_flashcards),
|
title = stringResource(R.string.intro_title_flashcards),
|
||||||
description = stringResource(R.string.intro_desc_flashcards),
|
description = stringResource(R.string.intro_desc_flashcards),
|
||||||
content = { FlashcardTopicsPreview() }
|
content = { FlashcardTopicsPreview() }
|
||||||
),
|
),
|
||||||
IntroPageData(
|
IntroPageData(
|
||||||
title = stringResource(R.string.intro_title_practice),
|
title = stringResource(R.string.intro_title_practice),
|
||||||
description = stringResource(R.string.intro_desc_practice),
|
description = stringResource(R.string.intro_desc_practice),
|
||||||
content = { IconContent(iconRes = R.drawable.ic_inro_practice) }
|
content = { IconContent(iconRes = R.drawable.ic_inro_practice) }
|
||||||
),
|
),
|
||||||
IntroPageData(
|
IntroPageData(
|
||||||
title = stringResource(R.string.intro_title_learning_journey),
|
title = stringResource(R.string.intro_title_learning_journey),
|
||||||
description = stringResource(R.string.intro_desc_learning_journey),
|
description = stringResource(R.string.intro_desc_learning_journey),
|
||||||
content = { IconContent(iconRes = R.drawable.ic_intro_learning_journey)}
|
content = { IconContent(iconRes = R.drawable.ic_intro_learning_journey) }
|
||||||
),
|
),
|
||||||
IntroPageData(
|
IntroPageData(
|
||||||
title = stringResource(R.string.intro_title_categories),
|
title = stringResource(R.string.intro_title_categories),
|
||||||
description = stringResource(R.string.intro_desc_categories),
|
description = stringResource(R.string.intro_desc_categories),
|
||||||
content = { IconContent(iconRes = R.drawable.ic_intro_categories) }
|
content = { IconContent(iconRes = R.drawable.ic_intro_categories) }
|
||||||
),
|
),
|
||||||
IntroPageData(
|
IntroPageData(
|
||||||
title = stringResource(R.string.intro_title_progress),
|
title = stringResource(R.string.intro_title_progress),
|
||||||
description = stringResource(R.string.intro_desc_progress),
|
description = stringResource(R.string.intro_desc_progress),
|
||||||
content = { IconContent(iconRes = R.drawable.ic_intro_track_progress) }
|
content = { IconContent(iconRes = R.drawable.ic_intro_track_progress) }
|
||||||
),
|
),
|
||||||
IntroPageData(
|
IntroPageData(
|
||||||
title = stringResource(R.string.intro_need_help),
|
title = stringResource(R.string.intro_need_help),
|
||||||
description = stringResource(R.string.intro_if_you_need_help_you),
|
description = stringResource(R.string.intro_if_you_need_help_you),
|
||||||
content = { IconContent(iconRes = R.drawable.ic_intro_help) }
|
content = { IconContent(iconRes = R.drawable.ic_intro_help) }
|
||||||
),
|
),
|
||||||
IntroPageData(
|
IntroPageData(
|
||||||
title = stringResource(R.string.intro_title_beta),
|
title = stringResource(R.string.intro_title_beta),
|
||||||
description = stringResource(R.string.intro_desc_beta),
|
description = stringResource(R.string.intro_desc_beta),
|
||||||
content = { IconContent(iconRes = R.drawable.ic_icon_construction) }
|
content = { IconContent(iconRes = R.drawable.ic_icon_construction) }
|
||||||
),
|
),
|
||||||
IntroPageData(
|
IntroPageData(
|
||||||
title = stringResource(R.string.intro_title_all_set),
|
title = stringResource(R.string.intro_title_all_set),
|
||||||
description = stringResource(R.string.intro_desc_all_set),
|
description = stringResource(R.string.intro_desc_all_set),
|
||||||
content = { IconContent(iconRes = R.drawable.ic_intro_robot) }
|
content = { IconContent(iconRes = R.drawable.ic_intro_robot) }
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val pagerState = rememberPagerState(pageCount = { pages.size })
|
val pagerState = rememberPagerState(pageCount = { pages.size })
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -128,7 +133,6 @@ fun IntroNavHost(onIntroFinished: () -> Unit) {
|
|||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
.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(
|
eu.gaudian.translator.view.composable.SecondaryButton(
|
||||||
onClick = { onIntroFinished() },
|
onClick = { onIntroFinished() },
|
||||||
text = stringResource(R.string.intro_skip),
|
text = stringResource(R.string.intro_skip),
|
||||||
@@ -145,7 +149,9 @@ fun IntroNavHost(onIntroFinished: () -> Unit) {
|
|||||||
) {
|
) {
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
) { pageIndex ->
|
) { pageIndex ->
|
||||||
IntroPage(pageData = pages[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),
|
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()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -189,9 +195,9 @@ private fun IntroPage(pageData: IntroPageData) {
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.CenterVertically),
|
verticalArrangement = Arrangement.spacedBy(24.dp, Alignment.CenterVertically),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxHeight()
|
.fillMaxSize() // Fixed: This was previously fillMaxHeight()
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
.verticalScroll(rememberScrollState()) // Allow scrolling for larger hint content
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Text(
|
Text(
|
||||||
@@ -234,15 +240,14 @@ private fun PagerIndicator(pageCount: Int, currentPage: Int) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun IconContent(iconRes: Int) {
|
private fun IconContent(iconRes: Int) {
|
||||||
Box(modifier = Modifier.clip(RoundedCornerShape(16.dp))) {
|
Box(modifier = Modifier.clip(RoundedCornerShape(16.dp))) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = iconRes),
|
painter = painterResource(id = iconRes),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
tint = Color.Unspecified,
|
tint = Color.Unspecified,
|
||||||
modifier = Modifier.size(250.dp)
|
modifier = Modifier.size(250.dp)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun FlashcardTopicsPreview() {
|
private fun FlashcardTopicsPreview() {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import eu.gaudian.translator.R
|
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.HintBottomSheet
|
||||||
import eu.gaudian.translator.view.hints.LocalShowHints
|
import eu.gaudian.translator.view.hints.LocalShowHints
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ import eu.gaudian.translator.view.hints.LocalShowHints
|
|||||||
fun AppDialog(
|
fun AppDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
title: (@Composable () -> Unit)? = null,
|
title: (@Composable () -> Unit)? = null,
|
||||||
hintContent: @Composable (() -> Unit)? = null,
|
hintContent: Hint? = null,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
// 1. Swipe Resistance: Prevent accidental dismissal
|
// 1. Swipe Resistance: Prevent accidental dismissal
|
||||||
@@ -98,7 +99,7 @@ fun AppDialog(
|
|||||||
if (showBottomSheet) {
|
if (showBottomSheet) {
|
||||||
EnhancedHintBottomSheet(
|
EnhancedHintBottomSheet(
|
||||||
onDismissRequest = { showBottomSheet = false },
|
onDismissRequest = { showBottomSheet = false },
|
||||||
content = hintContent,
|
content = {hintContent?.Render()},
|
||||||
parentTitle = title
|
parentTitle = title
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -156,7 +157,7 @@ fun AppAlertDialog(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun DialogHeader(
|
private fun DialogHeader(
|
||||||
title: (@Composable () -> Unit)?,
|
title: (@Composable () -> Unit)?,
|
||||||
hintContent: @Composable (() -> Unit)?,
|
hintContent: Hint? = null,
|
||||||
onHintClick: () -> Unit,
|
onHintClick: () -> Unit,
|
||||||
onCloseClick: () -> Unit
|
onCloseClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -327,7 +328,6 @@ fun AppDialogPreview() {
|
|||||||
AppDialog(
|
AppDialog(
|
||||||
onDismissRequest = {},
|
onDismissRequest = {},
|
||||||
title = { Text("Dialog Title") },
|
title = { Text("Dialog Title") },
|
||||||
hintContent = { Text("This is a hint.") },
|
|
||||||
content = {
|
content = {
|
||||||
Column {
|
Column {
|
||||||
Text("Content line 1")
|
Text("Content line 1")
|
||||||
@@ -378,7 +378,6 @@ fun AppDialogLongContentPreview() {
|
|||||||
AppDialog(
|
AppDialog(
|
||||||
onDismissRequest = {},
|
onDismissRequest = {},
|
||||||
title = { Text("Long Content Dialog") },
|
title = { Text("Long Content Dialog") },
|
||||||
hintContent = { Text("Hint for long content dialog") },
|
|
||||||
content = {
|
content = {
|
||||||
Column {
|
Column {
|
||||||
Text("This is a long content dialog to test scrolling")
|
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.AppIcons
|
||||||
import eu.gaudian.translator.view.composable.AppOutlinedTextField
|
import eu.gaudian.translator.view.composable.AppOutlinedTextField
|
||||||
import eu.gaudian.translator.view.composable.MultipleLanguageDropdown
|
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.CategoryViewModel
|
||||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ fun AddCategoryDialog(
|
|||||||
AppDialog(
|
AppDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = { Text(stringResource(R.string.label_add_category)) },
|
title = { Text(stringResource(R.string.label_add_category)) },
|
||||||
hintContent = { CategoryHint() },
|
hintContent = HintDefinition.CATEGORY.hint(),
|
||||||
content = {
|
content = {
|
||||||
Column(modifier = Modifier.fillMaxWidth()) {
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
SingleChoiceSegmentedButtonRow(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.InspiringSearchField
|
||||||
import eu.gaudian.translator.view.composable.SourceLanguageDropdown
|
import eu.gaudian.translator.view.composable.SourceLanguageDropdown
|
||||||
import eu.gaudian.translator.view.composable.TargetLanguageDropdown
|
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.LanguageViewModel
|
||||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -109,7 +109,7 @@ fun ImportDialogContent(
|
|||||||
AppDialog(
|
AppDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = { Text(descriptionText) },
|
title = { Text(descriptionText) },
|
||||||
hintContent = { ImportVocabularyHint() },
|
hintContent = HintDefinition.IMPORT.hint(),
|
||||||
content = {
|
content = {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
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.AppCheckbox
|
||||||
import eu.gaudian.translator.view.composable.AppScaffold
|
import eu.gaudian.translator.view.composable.AppScaffold
|
||||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||||
import eu.gaudian.translator.view.hints.getVocabularyReviewHint
|
import eu.gaudian.translator.view.hints.HintDefinition
|
||||||
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
import eu.gaudian.translator.viewmodel.CategoryViewModel
|
||||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ fun VocabularyReviewScreen(
|
|||||||
topBar = {
|
topBar = {
|
||||||
AppTopAppBar(
|
AppTopAppBar(
|
||||||
title = { Text(stringResource(R.string.found_items)) },
|
title = { Text(stringResource(R.string.found_items)) },
|
||||||
hintContent = getVocabularyReviewHint()
|
hintContent = HintDefinition.REVIEW.hint()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { 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
|
package eu.gaudian.translator.view.hints
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -9,9 +11,9 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import dev.jeziellago.compose.markdowntext.MarkdownText
|
import dev.jeziellago.compose.markdowntext.MarkdownText
|
||||||
|
import eu.gaudian.translator.utils.Log
|
||||||
|
|
||||||
private const val TAG = "MarkdownHint"
|
private const val TAG = "MarkdownHint"
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ sealed class HintElement {
|
|||||||
data class UIElement(val composable: @Composable () -> Unit) : 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.
|
* The file is loaded from assets based on the current device locale.
|
||||||
* Follows Android's locale-qualified resource pattern:
|
* Follows Android's locale-qualified resource pattern:
|
||||||
* - assets/hints/ - Default (English)
|
* - assets/hints/ - Default (English)
|
||||||
@@ -56,7 +58,7 @@ fun RenderHintElement(element: HintElement) {
|
|||||||
androidx.compose.foundation.layout.Row(
|
androidx.compose.foundation.layout.Row(
|
||||||
modifier = Modifier.padding(top = 4.dp)
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
) {
|
) {
|
||||||
androidx.compose.material3.Text(
|
Text(
|
||||||
text = "[DEBUG: ${element.fileName}]",
|
text = "[DEBUG: ${element.fileName}]",
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
color = MaterialTheme.colorScheme.error
|
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.
|
* Automatically loads the correct locale version based on device settings.
|
||||||
* Falls back to English default if localized version is not available.
|
* 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)
|
// Try localized version (folder has suffix, filename doesn't)
|
||||||
val localizedPath = "hints$suffix/$fileName.md"
|
val localizedPath = "hints$suffix/$fileName.md"
|
||||||
|
|
||||||
android.util.Log.d(TAG, "Loading hint: $fileName")
|
Log.d(TAG, "Loading hint: $fileName")
|
||||||
android.util.Log.d(TAG, "Device locale: ${locale.language}_${locale.country}")
|
Log.d(TAG, "Device locale: ${locale.language}_${locale.country}")
|
||||||
android.util.Log.d(TAG, "Localized path: $localizedPath")
|
Log.d(TAG, "Localized path: $localizedPath")
|
||||||
|
|
||||||
val localized = MarkdownHintLoader.loadFromAssets(context, localizedPath)
|
val localized = MarkdownHintLoader.loadFromAssets(context, localizedPath)
|
||||||
if (localized != null) {
|
if (localized != null) {
|
||||||
android.util.Log.d(TAG, "Found localized version at: $localizedPath")
|
Log.d(TAG, "Found localized version at: $localizedPath")
|
||||||
localized
|
localized
|
||||||
} else {
|
} else {
|
||||||
// Fall back to English default in hints folder
|
// Fall back to English default in hints folder
|
||||||
val defaultPath = "hints/$fileName.md"
|
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)
|
val default = MarkdownHintLoader.loadFromAssets(context, defaultPath)
|
||||||
if (default != null) {
|
if (default != null) {
|
||||||
android.util.Log.d(TAG, "Found default version at: $defaultPath")
|
Log.d(TAG, "Found default version at: $defaultPath")
|
||||||
} else {
|
} 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
|
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.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.compose.rememberNavController
|
|
||||||
import eu.gaudian.translator.R
|
import eu.gaudian.translator.R
|
||||||
import eu.gaudian.translator.ui.theme.ThemePreviews
|
|
||||||
import eu.gaudian.translator.view.LocalShowExperimentalFeatures
|
import eu.gaudian.translator.view.LocalShowExperimentalFeatures
|
||||||
import eu.gaudian.translator.view.composable.AppCard
|
import eu.gaudian.translator.view.composable.AppCard
|
||||||
import eu.gaudian.translator.view.composable.AppIcons
|
import eu.gaudian.translator.view.composable.AppIcons
|
||||||
@@ -42,15 +40,16 @@ fun HintsOverviewScreen(
|
|||||||
val showExperimental = LocalShowExperimentalFeatures.current
|
val showExperimental = LocalShowExperimentalFeatures.current
|
||||||
|
|
||||||
// Get hints using the new function-based approach
|
// Get hints using the new function-based approach
|
||||||
val importHint = getImportVocabularyHint()
|
val importHint = HintDefinition.IMPORT.hint()
|
||||||
val addModelScanHint = getAddModelScanHint()
|
val addModelScanHint = HintDefinition.ADD_MODEL_SCAN.hint()
|
||||||
val dictionaryOptionsHint = getDictionaryOptionsHint()
|
val dictionaryOptionsHint = HintDefinition.DICTIONARY_OPTIONS.hint()
|
||||||
val translationScreenHint = getTranslationScreenHint()
|
val translationScreenHint = HintDefinition.TRANSLATION.hint()
|
||||||
val categoryHint = getCategoryHint()
|
val categoryHint = HintDefinition.CATEGORY.hint()
|
||||||
val learningStagesHint = getLearningStagesHint()
|
val learningStagesHint = HintDefinition.LEARNING_STAGES.hint()
|
||||||
val sortingScreenHint = getSortingScreenHint()
|
val sortingScreenHint = HintDefinition.SORTING.hint()
|
||||||
val vocabularyProgressHint = getVocabularyProgressHint()
|
val vocabularyProgressHint = HintDefinition.VOCABULARY_PROGRESS.hint()
|
||||||
val apiKeyHint = getApiKeyHint()
|
val apiKeyHint = HintDefinition.API_KEY.hint()
|
||||||
|
|
||||||
|
|
||||||
val hintGroups = remember(showExperimental, importHint, addModelScanHint, dictionaryOptionsHint, translationScreenHint) {
|
val hintGroups = remember(showExperimental, importHint, addModelScanHint, dictionaryOptionsHint, translationScreenHint) {
|
||||||
val allGroups = listOf(
|
val allGroups = listOf(
|
||||||
@@ -133,11 +132,7 @@ fun HintsOverviewScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ThemePreviews
|
|
||||||
@Composable
|
|
||||||
fun HintsOverviewScreenPreview() {
|
|
||||||
HintsOverviewScreen(navController = rememberNavController())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun HintHeader(
|
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
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internationalization system for markdown hints.
|
* Internationalization system for Markdown hints.
|
||||||
*
|
*
|
||||||
* This follows Android's locale-qualified resource pattern:
|
* This follows Android's locale-qualified resource pattern:
|
||||||
* - assets/hints/ - Default (English)
|
* - assets/hints/ - Default (English)
|
||||||
@@ -18,131 +18,22 @@ import java.util.Locale
|
|||||||
*/
|
*/
|
||||||
object MarkdownHintLoader {
|
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 {
|
fun getCurrentLocale(context: Context): Locale {
|
||||||
return context.resources.configuration.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.
|
* Load content from assets.
|
||||||
*/
|
*/
|
||||||
fun loadFromAssets(context: Context, fileName: String): String? {
|
fun loadFromAssets(context: Context, fileName: String): String? {
|
||||||
return try {
|
return try {
|
||||||
context.assets.open(fileName).bufferedReader().use { it.readText() }
|
context.assets.open(fileName).bufferedReader().use { it.readText() }
|
||||||
} catch (e: Exception) {
|
} catch (_: Exception) {
|
||||||
null
|
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.
|
* 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.AppSwitch
|
||||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||||
import eu.gaudian.translator.view.composable.ModelBadges
|
import eu.gaudian.translator.view.composable.ModelBadges
|
||||||
import eu.gaudian.translator.view.hints.AddModelScanHint
|
import eu.gaudian.translator.view.hints.HintDefinition
|
||||||
import eu.gaudian.translator.view.hints.getAddModelScanHint
|
|
||||||
import eu.gaudian.translator.viewmodel.ApiViewModel
|
import eu.gaudian.translator.viewmodel.ApiViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -141,7 +140,7 @@ fun AddModelScreen(navController: NavController, providerKey: String) {
|
|||||||
Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back))
|
Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hintContent = getAddModelScanHint()
|
hintContent = HintDefinition.ADD_MODEL_SCAN.hint()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { 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.PrimaryButton
|
||||||
import eu.gaudian.translator.view.composable.SecondaryButton
|
import eu.gaudian.translator.view.composable.SecondaryButton
|
||||||
import eu.gaudian.translator.view.composable.TabItem
|
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.ApiKeyManagementState
|
||||||
import eu.gaudian.translator.viewmodel.ApiViewModel
|
import eu.gaudian.translator.viewmodel.ApiViewModel
|
||||||
import eu.gaudian.translator.viewmodel.ProviderState
|
import eu.gaudian.translator.viewmodel.ProviderState
|
||||||
@@ -121,7 +121,7 @@ fun ApiKeyScreen(navController: NavController) {
|
|||||||
Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back))
|
Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hintContent = getApiKeyHint()
|
hintContent = HintDefinition.API_KEY.hint()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { 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.AppTopAppBar
|
||||||
import eu.gaudian.translator.view.composable.OptionItemSwitch
|
import eu.gaudian.translator.view.composable.OptionItemSwitch
|
||||||
import eu.gaudian.translator.view.dictionary.DictionaryManagerContent
|
import eu.gaudian.translator.view.dictionary.DictionaryManagerContent
|
||||||
import eu.gaudian.translator.view.hints.getDictionaryOptionsHint
|
import eu.gaudian.translator.view.hints.HintDefinition
|
||||||
import eu.gaudian.translator.viewmodel.ApiViewModel
|
import eu.gaudian.translator.viewmodel.ApiViewModel
|
||||||
import eu.gaudian.translator.viewmodel.DictionaryViewModel
|
import eu.gaudian.translator.viewmodel.DictionaryViewModel
|
||||||
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
||||||
@@ -72,7 +72,7 @@ fun DictionaryOptionsScreen(
|
|||||||
Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back))
|
Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hintContent = getDictionaryOptionsHint()
|
hintContent = HintDefinition.DICTIONARY_OPTIONS.hint()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
|||||||
@@ -7,16 +7,9 @@ import androidx.navigation.NavGraphBuilder
|
|||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.navigation
|
import androidx.navigation.navigation
|
||||||
import eu.gaudian.translator.view.composable.Screen
|
import eu.gaudian.translator.view.composable.Screen
|
||||||
import eu.gaudian.translator.view.hints.ApiHintScreen
|
import eu.gaudian.translator.view.hints.HintDefinition
|
||||||
import eu.gaudian.translator.view.hints.CategoryHintScreenWrapper
|
import eu.gaudian.translator.view.hints.HintScreen
|
||||||
import eu.gaudian.translator.view.hints.DictionaryHintScreen
|
|
||||||
import eu.gaudian.translator.view.hints.HintsOverviewScreen
|
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
|
// Defines the routes for the settings graph to avoid using raw strings
|
||||||
object SettingsRoutes {
|
object SettingsRoutes {
|
||||||
@@ -114,31 +107,31 @@ fun NavGraphBuilder.settingsGraph(navController: NavController) {
|
|||||||
HintsOverviewScreen(navController = navController)
|
HintsOverviewScreen(navController = navController)
|
||||||
}
|
}
|
||||||
composable(SettingsRoutes.HINTS_CATEGORIES) {
|
composable(SettingsRoutes.HINTS_CATEGORIES) {
|
||||||
CategoryHintScreenWrapper(navController = navController)
|
HintScreen(navController, HintDefinition.CATEGORY)
|
||||||
}
|
}
|
||||||
composable(SettingsRoutes.HINTS_DICTIONARY) {
|
composable(SettingsRoutes.HINTS_DICTIONARY) {
|
||||||
DictionaryHintScreen(navController = navController)
|
HintScreen(navController, HintDefinition.DICTIONARY_OPTIONS)
|
||||||
}
|
}
|
||||||
composable(SettingsRoutes.HINTS_IMPORT) {
|
composable(SettingsRoutes.HINTS_IMPORT) {
|
||||||
ImportHintScreen(navController = navController)
|
HintScreen(navController, HintDefinition.IMPORT)
|
||||||
}
|
}
|
||||||
composable(SettingsRoutes.HINTS_SORTING) {
|
composable(SettingsRoutes.HINTS_SORTING) {
|
||||||
SortingHintScreen(navController = navController)
|
HintScreen(navController, HintDefinition.SORTING)
|
||||||
}
|
}
|
||||||
composable(SettingsRoutes.HINTS_STAGES) {
|
composable(SettingsRoutes.HINTS_STAGES) {
|
||||||
StagesHintScreen(navController = navController)
|
HintScreen(navController, HintDefinition.LEARNING_STAGES)
|
||||||
}
|
}
|
||||||
composable(SettingsRoutes.HINTS_TRANSLATION) {
|
composable(SettingsRoutes.HINTS_TRANSLATION) {
|
||||||
TranslationHintScreen(navController = navController)
|
HintScreen(navController, HintDefinition.TRANSLATION)
|
||||||
}
|
}
|
||||||
composable(SettingsRoutes.HINTS_SCAN) {
|
composable(SettingsRoutes.HINTS_SCAN) {
|
||||||
ScanHintScreen(navController = navController)
|
HintScreen(navController, HintDefinition.ADD_MODEL_SCAN)
|
||||||
}
|
}
|
||||||
composable(SettingsRoutes.HINTS_API) {
|
composable(SettingsRoutes.HINTS_API) {
|
||||||
ApiHintScreen(navController = navController)
|
HintScreen(navController, HintDefinition.API_KEY)
|
||||||
}
|
}
|
||||||
composable(SettingsRoutes.HINTS_VOCABULARY_PROGRESS) {
|
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.AppScaffold
|
||||||
import eu.gaudian.translator.view.composable.AppSlider
|
import eu.gaudian.translator.view.composable.AppSlider
|
||||||
import eu.gaudian.translator.view.composable.AppTopAppBar
|
import eu.gaudian.translator.view.composable.AppTopAppBar
|
||||||
import eu.gaudian.translator.view.hints.VocabularyProgressHint
|
import eu.gaudian.translator.view.hints.HintDefinition
|
||||||
import eu.gaudian.translator.view.hints.getVocabularyProgressHint
|
|
||||||
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
||||||
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
import eu.gaudian.translator.viewmodel.VocabularyViewModel
|
||||||
import kotlin.math.exp
|
import kotlin.math.exp
|
||||||
@@ -86,7 +85,7 @@ fun VocabularyProgressOptionsScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Here is the new hint content
|
// Here is the new hint content
|
||||||
hintContent = getVocabularyProgressHint()
|
hintContent = HintDefinition.VOCABULARY_PROGRESS.hint()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ import eu.gaudian.translator.view.NoConnectionScreen
|
|||||||
import eu.gaudian.translator.view.composable.AppCard
|
import eu.gaudian.translator.view.composable.AppCard
|
||||||
import eu.gaudian.translator.view.composable.AppOutlinedCard
|
import eu.gaudian.translator.view.composable.AppOutlinedCard
|
||||||
import eu.gaudian.translator.view.dialogs.AddVocabularyDialog
|
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.view.settings.SettingsRoutes
|
||||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||||
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
import eu.gaudian.translator.viewmodel.SettingsViewModel
|
||||||
@@ -167,7 +167,7 @@ private fun LoadedTranslationContent(
|
|||||||
TopBarActions(
|
TopBarActions(
|
||||||
languageViewModel = languageViewModel,
|
languageViewModel = languageViewModel,
|
||||||
onSettingsClick = onSettingsClick,
|
onSettingsClick = onSettingsClick,
|
||||||
hintContent = { TranslationScreenHint() }
|
hintContent = { HintDefinition.TRANSLATION.Render() }
|
||||||
)
|
)
|
||||||
|
|
||||||
AppCard(modifier = Modifier.padding(8.dp, end = 8.dp, bottom = 8.dp, top = 0.dp)) {
|
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.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -391,7 +390,6 @@ private fun ExerciseTypeSelector(
|
|||||||
onTypeSelected: (VocabularyExerciseType) -> Unit,
|
onTypeSelected: (VocabularyExerciseType) -> Unit,
|
||||||
) {
|
) {
|
||||||
// Using FlowRow for a more flexible layout that wraps to the next line if needed
|
// Using FlowRow for a more flexible layout that wraps to the next line if needed
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
FlowRow(
|
FlowRow(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally),
|
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.composable.SingleLanguageDropDown
|
||||||
import eu.gaudian.translator.view.dialogs.CategoryDropdown
|
import eu.gaudian.translator.view.dialogs.CategoryDropdown
|
||||||
import eu.gaudian.translator.view.dialogs.CreateCategoryListDialog
|
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.CategoryViewModel
|
||||||
import eu.gaudian.translator.viewmodel.LanguageConfigViewModel
|
import eu.gaudian.translator.viewmodel.LanguageConfigViewModel
|
||||||
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
import eu.gaudian.translator.viewmodel.LanguageViewModel
|
||||||
@@ -236,7 +236,7 @@ fun VocabularySortingScreen(
|
|||||||
Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back))
|
Icon(AppIcons.ArrowBack, contentDescription = stringResource(R.string.cd_back))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
hintContent = getSortingScreenHint()
|
hintContent = HintDefinition.SORTING.hint()
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user