implement markdown-based hint system and add MarkdownHint component

This commit is contained in:
jonasgaudian
2026-02-14 15:53:05 +01:00
parent f829174bcb
commit 306d0c7432
12 changed files with 415 additions and 3 deletions

View File

@@ -21,6 +21,11 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppIcons
/**
* TODO: Migrate to markdown-based hint format.
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
* See MarkdownHint.kt for implementation details.
*/
object ApiKeyHint: LegacyHint() {
override val titleRes: Int = R.string.hint_how_to_connect_to_an_ai

View File

@@ -21,6 +21,11 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppIcons
/**
* TODO: Migrate to markdown-based hint format.
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
* See MarkdownHint.kt for implementation details.
*/
object CategoryHint : LegacyHint() {
override val titleRes: Int = R.string.category_hint_intro

View File

@@ -26,6 +26,11 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppIcons
/**
* TODO: Migrate to markdown-based hint format.
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
* See MarkdownHint.kt for implementation details.
*/
object CategoryHintScreen : LegacyHint() {
override val titleRes: Int = R.string.category_hint_intro

View File

@@ -0,0 +1,145 @@
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)
)
}
}

View File

@@ -32,6 +32,11 @@ private data class LearningStage(
val interval: String? = null
)
/**
* TODO: Migrate to markdown-based hint format.
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
* See MarkdownHint.kt for implementation details.
*/
object LearningStagesHint : LegacyHint() {
override val titleRes: Int = R.string.learning_stages_title

View File

@@ -0,0 +1,147 @@
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() }
}
}

View File

@@ -38,6 +38,11 @@ import eu.gaudian.translator.view.composable.AppButton
import eu.gaudian.translator.view.composable.AppIcons
import eu.gaudian.translator.view.composable.AppOutlinedTextField
/**
* TODO: Migrate to markdown-based hint format.
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
* See MarkdownHint.kt for implementation details.
*/
object SortingScreenHint : LegacyHint() {
override val titleRes: Int = R.string.sorting_hint_title

View File

@@ -21,6 +21,11 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppIcons
/**
* TODO: Migrate to markdown-based hint format.
* Create a .md file in assets/hints/ and use MarkdownHint composable instead.
* See MarkdownHint.kt for implementation details.
*/
object VocabularyProgressHint : LegacyHint() {
override val titleRes: Int = R.string.hint_vocabulary_progress_hint_title