-
-
Notifications
You must be signed in to change notification settings - Fork 59
/
Markdown.kt
124 lines (114 loc) · 4.63 KB
/
Markdown.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package me.mudkip.moememos.ui.component
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.material3.Checkbox
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.sp
import me.mudkip.moememos.ext.appendMarkdown
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
import org.intellij.markdown.parser.MarkdownParser
@OptIn(ExperimentalTextApi::class)
@Composable
fun Markdown(
text: String,
modifier: Modifier = Modifier,
textAlign: TextAlign? = null,
imageContent: @Composable (url: String) -> Unit = {},
checkboxChange: (checked: Boolean, startOffset: Int, endOffset: Int) -> Unit = { _, _, _ -> }
) {
val linkColor = MaterialTheme.colorScheme.primary
val bulletColor = MaterialTheme.colorScheme.tertiary
val headlineLarge = MaterialTheme.typography.headlineLarge
val headlineMedium = MaterialTheme.typography.headlineMedium
val headlineSmall = MaterialTheme.typography.headlineSmall
val uriHandler = LocalUriHandler.current
BoxWithConstraints {
val (annotatedString, inlineContent) = remember(text, maxWidth) {
val markdownAst = MarkdownParser(GFMFlavourDescriptor()).parse(MarkdownElementTypes.MARKDOWN_FILE, text, true)
val builder = AnnotatedString.Builder()
val inlineContent = HashMap<String, InlineTextContent>()
builder.appendMarkdown(
markdownText = text,
node = markdownAst,
depth = 0,
linkColor = linkColor,
onImage = { key, url ->
inlineContent[key] = InlineTextContent(
Placeholder(maxWidth.value.sp, (maxWidth.value * 9f / 16f).sp, PlaceholderVerticalAlign.AboveBaseline),
) {
imageContent(url)
}
},
onCheckbox = { key, startOffset, endOffset ->
inlineContent[key] = InlineTextContent(
Placeholder(20.sp, 20.sp, PlaceholderVerticalAlign.Center)
) {
val checkboxText = text.substring(startOffset, endOffset)
Checkbox(checked = checkboxText.length > 1 && checkboxText[1] != ' ', onCheckedChange = {
checkboxChange(it, startOffset, endOffset)
})
}
},
maxWidth = maxWidth.value,
bulletColor = bulletColor,
headlineLarge = headlineLarge,
headlineMedium = headlineMedium,
headlineSmall = headlineSmall
)
Pair(builder.toAnnotatedString(), inlineContent)
}
ClickableText(
text = annotatedString,
modifier = modifier,
textAlign = textAlign,
inlineContent = inlineContent,
onClick = {
annotatedString.getUrlAnnotations(it, it)
.firstOrNull()?.let { url ->
uriHandler.openUri(url.item.url)
}
}
)
}
}
@Composable
fun ClickableText(
text: AnnotatedString,
modifier: Modifier = Modifier,
textAlign: TextAlign? = null,
inlineContent: Map<String, InlineTextContent> = mapOf(),
onClick: (Int) -> Unit
) {
val layoutResult = remember { mutableStateOf<TextLayoutResult?>(null) }
val pressIndicator = Modifier.pointerInput(onClick) {
detectTapGestures { pos ->
layoutResult.value?.let { layoutResult ->
onClick(layoutResult.getOffsetForPosition(pos))
}
}
}
Text(
text = text,
modifier = modifier.then(pressIndicator),
textAlign = textAlign,
inlineContent = inlineContent,
onTextLayout = {
layoutResult.value = it
}
)
}