Markdown editor: improve list handling
This commit is contained in:
parent
328a69e75b
commit
e726ff1ffd
@ -23,8 +23,10 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.input.OffsetMapping
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.TransformedText
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||
@ -32,8 +34,8 @@ import org.intellij.markdown.parser.MarkdownParser
|
||||
|
||||
@Composable
|
||||
fun MarkdownEditor(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
value: TextFieldValue,
|
||||
onValueChange: (TextFieldValue) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
placeholder: (@Composable () -> Unit)? = null
|
||||
) {
|
||||
@ -58,7 +60,46 @@ fun MarkdownEditor(
|
||||
|
||||
BasicTextField(
|
||||
value = value,
|
||||
onValueChange = onValueChange,
|
||||
onValueChange = {
|
||||
val cursorPosition = if (it.selection.collapsed) it.selection.start else null
|
||||
// If:
|
||||
// - multiple chars are selected
|
||||
// - the last action was not an insert
|
||||
// - the cursor char before the selection is not a newline
|
||||
// do nothing.
|
||||
if (cursorPosition == null || it.text.length <= value.text.length || it.text.getOrNull(
|
||||
cursorPosition - 1
|
||||
) != '\n'
|
||||
) {
|
||||
onValueChange(it)
|
||||
} else {
|
||||
// else check if the previous line was a list, if yes, add a list item
|
||||
val prevLine = it.text.substring(0, cursorPosition - 1).substringAfterLast('\n')
|
||||
val leadingSpaces = prevLine.takeWhile { it == ' ' }
|
||||
val prevLineWithoutLeadingSpaces = prevLine.trimStart()
|
||||
val listMarker = leadingSpaces + when {
|
||||
prevLineWithoutLeadingSpaces.startsWith("- [ ] ") -> "- [ ] "
|
||||
prevLineWithoutLeadingSpaces.startsWith("- [x] ") -> "- [ ] "
|
||||
prevLineWithoutLeadingSpaces.startsWith("- ") -> "- "
|
||||
prevLineWithoutLeadingSpaces.startsWith("* ") -> "* "
|
||||
prevLineWithoutLeadingSpaces.startsWith("+ ") -> "+ "
|
||||
prevLineWithoutLeadingSpaces.startsWith("1. ") -> "1. "
|
||||
else -> {
|
||||
onValueChange(it)
|
||||
return@BasicTextField
|
||||
}
|
||||
}
|
||||
onValueChange(
|
||||
it.copy(
|
||||
text = it.text.substring(
|
||||
0,
|
||||
cursorPosition
|
||||
) + listMarker + it.text.substring(cursorPosition),
|
||||
selection = TextRange(cursorPosition + listMarker.length)
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = modifier.focusRequester(focusRequester),
|
||||
textStyle = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = LocalContentColor.current,
|
||||
@ -72,7 +113,7 @@ fun MarkdownEditor(
|
||||
)
|
||||
|
||||
} else {
|
||||
if (placeholder != null && value.isBlank()) {
|
||||
if (placeholder != null && value.text.isBlank()) {
|
||||
Box(
|
||||
modifier = modifier.clickable(
|
||||
indication = null,
|
||||
@ -91,14 +132,14 @@ fun MarkdownEditor(
|
||||
}
|
||||
} else {
|
||||
MarkdownText(
|
||||
value,
|
||||
value.text,
|
||||
modifier = modifier.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
) {
|
||||
focused = true
|
||||
},
|
||||
onTextChange = onValueChange,
|
||||
onTextChange = { onValueChange(TextFieldValue(it)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@ -93,7 +94,7 @@ fun NotesWidget(
|
||||
|
||||
}
|
||||
)
|
||||
AnimatedVisibility(isLastWidget == false && text.isBlank()) {
|
||||
AnimatedVisibility(isLastWidget == false && text.text.isBlank()) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
viewModel.dismissNote()
|
||||
@ -105,7 +106,7 @@ fun NotesWidget(
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(text.isNotBlank()) {
|
||||
AnimatedVisibility(text.text.isNotBlank()) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
Box(
|
||||
modifier = Modifier
|
||||
@ -168,7 +169,7 @@ fun NotesWidget(
|
||||
}
|
||||
} else {
|
||||
val content = text
|
||||
viewModel.setText("")
|
||||
viewModel.setText(TextFieldValue(""))
|
||||
lifecycleOwner.lifecycleScope.launch {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = context.getString(R.string.notes_widget_dismissed),
|
||||
|
||||
@ -4,6 +4,7 @@ import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
@ -26,7 +27,7 @@ class NotesWidgetVM(
|
||||
) : ViewModel() {
|
||||
private val widget = MutableStateFlow<NotesWidget?>(null)
|
||||
|
||||
val noteText = mutableStateOf(widget.value?.config?.storedText ?: "")
|
||||
val noteText = mutableStateOf(TextFieldValue(widget.value?.config?.storedText ?: ""))
|
||||
|
||||
val isLastNoteWidget = widgetsService.countWidgets(NotesWidget.Type).map {
|
||||
it == 1
|
||||
@ -35,23 +36,23 @@ class NotesWidgetVM(
|
||||
fun updateWidget(widget: NotesWidget) {
|
||||
val oldId = this.widget.value?.id
|
||||
this.widget.value = widget
|
||||
if (widget.id != oldId) noteText.value = widget.config.storedText
|
||||
if (widget.id != oldId) noteText.value = TextFieldValue(widget.config.storedText)
|
||||
}
|
||||
|
||||
private var updateJob: Job? = null
|
||||
fun setText(text: String) {
|
||||
fun setText(text: TextFieldValue) {
|
||||
noteText.value = text
|
||||
updateJob?.cancel()
|
||||
val widget = widget.value ?: return
|
||||
updateJob = viewModelScope.launch {
|
||||
delay(1000)
|
||||
widgetsService.updateWidget(widget.copy(config = widget.config.copy(storedText = text)))
|
||||
widgetsService.updateWidget(widget.copy(config = widget.config.copy(storedText = text.text)))
|
||||
}
|
||||
}
|
||||
|
||||
fun exportNote(context: Context, uri: Uri) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val text = noteText.value
|
||||
val text = noteText.value.text
|
||||
Log.d("MM20", text)
|
||||
val outputStream = context.contentResolver.openOutputStream(uri)
|
||||
outputStream?.use {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user