From 7ed94aebf4a0a5969f69df308f37b8e2880a190a Mon Sep 17 00:00:00 2001 From: William Brawner Date: Sun, 5 May 2024 23:06:27 -0600 Subject: [PATCH] Remove padding from markdown editor and pre-render preview --- .../wbrawner/simplemarkdown/ui/MainScreen.kt | 72 +--------- .../simplemarkdown/ui/MarkdownTextField.kt | 124 ++++++++++++++++++ 2 files changed, 126 insertions(+), 70 deletions(-) create mode 100644 app/src/main/java/com/wbrawner/simplemarkdown/ui/MarkdownTextField.kt diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/ui/MainScreen.kt b/app/src/main/java/com/wbrawner/simplemarkdown/ui/MainScreen.kt index d4326f1..1367901 100644 --- a/app/src/main/java/com/wbrawner/simplemarkdown/ui/MainScreen.kt +++ b/app/src/main/java/com/wbrawner/simplemarkdown/ui/MainScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Menu @@ -47,10 +46,7 @@ import androidx.compose.material3.Tab import androidx.compose.material3.TabRow import androidx.compose.material3.Text import androidx.compose.material3.TextButton -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBar -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -63,17 +59,9 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextRange -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.input.KeyboardCapitalization -import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat.startActivity @@ -83,7 +71,6 @@ import com.wbrawner.simplemarkdown.EditorState import com.wbrawner.simplemarkdown.MarkdownViewModel import com.wbrawner.simplemarkdown.R import com.wbrawner.simplemarkdown.Route -import com.wbrawner.simplemarkdown.model.Readability import kotlinx.coroutines.delay import kotlinx.coroutines.flow.map import kotlinx.coroutines.isActive @@ -361,6 +348,7 @@ private fun TabbedMarkdownEditor( } HorizontalPager( modifier = Modifier.fillMaxSize(1f), state = pagerState, + beyondBoundsPageCount = 1, userScrollEnabled = !lockSwiping ) { page -> val keyboardController = LocalSoftwareKeyboardController.current @@ -384,19 +372,6 @@ private fun TabbedMarkdownEditor( } } -private fun String.annotate(enableReadability: Boolean): AnnotatedString { - if (!enableReadability) return AnnotatedString(this) - val readability = Readability(this) - val annotated = AnnotatedString.Builder(this) - for (sentence in readability.sentences()) { - var color = Color.Transparent - if (sentence.syllableCount() > 25) color = Color(229, 232, 42, 100) - if (sentence.syllableCount() > 35) color = Color(193, 66, 66, 100) - annotated.addStyle(SpanStyle(background = color), sentence.start(), sentence.end()) - } - return annotated.toAnnotatedString() -} - @Composable fun MarkdownNavigationDrawer( navigate: (Route) -> Unit, content: @Composable (drawerState: DrawerState) -> Unit @@ -482,51 +457,8 @@ fun MarkdownTopAppBar( IconButton(onClick = { onClick() }) { Icon(imageVector = icon, contentDescription = contentDescription) } - }, actions = actions ?: {}, - scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() - ) -} - -@Composable -fun MarkdownTextField( - modifier: Modifier, - reload: Int, - markdown: String, - setMarkdown: (String) -> Unit, - enableReadability: Boolean = false -) { - val (selection, setSelection) = remember { mutableStateOf(TextRange.Zero) } - val (composition, setComposition) = remember { mutableStateOf(null) } - val (textFieldValue, setTextFieldValue) = remember(reload) { - mutableStateOf(TextFieldValue(markdown.annotate(enableReadability), selection, composition)) - } - val setTextFieldAndViewModelValues: (TextFieldValue) -> Unit = { - setSelection(it.selection) - setComposition(it.composition) - setTextFieldValue(it.copy(annotatedString = it.text.annotate(enableReadability))) - setMarkdown(it.text) - } - - TextField( - modifier = modifier.imePadding(), - colors = TextFieldDefaults.colors( - focusedContainerColor = MaterialTheme.colorScheme.surface, - unfocusedContainerColor = MaterialTheme.colorScheme.surface, - disabledIndicatorColor = Color.Transparent, - errorIndicatorColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent - ), - value = textFieldValue, - onValueChange = setTextFieldAndViewModelValues, - placeholder = { - Text("Markdown here…") }, - textStyle = TextStyle.Default.copy( - fontFamily = FontFamily.Monospace, - color = MaterialTheme.colorScheme.onSurface - ), - keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences) + actions = actions ?: {}, ) } diff --git a/app/src/main/java/com/wbrawner/simplemarkdown/ui/MarkdownTextField.kt b/app/src/main/java/com/wbrawner/simplemarkdown/ui/MarkdownTextField.kt new file mode 100644 index 0000000..c6448fd --- /dev/null +++ b/app/src/main/java/com/wbrawner/simplemarkdown/ui/MarkdownTextField.kt @@ -0,0 +1,124 @@ +package com.wbrawner.simplemarkdown.ui + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.selection.LocalTextSelectionColors +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +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.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp +import com.wbrawner.simplemarkdown.model.Readability + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MarkdownTextField( + modifier: Modifier = Modifier, + markdown: String, + setMarkdown: (String) -> Unit, + reload: Int = 0, + enableReadability: Boolean = false, +) { + val (selection, setSelection) = remember { mutableStateOf(TextRange.Zero) } + val (composition, setComposition) = remember { mutableStateOf(null) } + val (textFieldValue, setTextFieldValue) = remember(reload) { + mutableStateOf(TextFieldValue(markdown.annotate(enableReadability), selection, composition)) + } + val setTextFieldAndViewModelValues: (TextFieldValue) -> Unit = { + setSelection(it.selection) + setComposition(it.composition) + setTextFieldValue(it.copy(annotatedString = it.text.annotate(enableReadability))) + setMarkdown(it.text) + } + val colors = TextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.surface, + unfocusedContainerColor = MaterialTheme.colorScheme.surface, + disabledIndicatorColor = Color.Transparent, + errorIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent + ) + Column( + modifier = modifier + .fillMaxSize() + .imePadding() + .verticalScroll( + rememberScrollState() + ) + ) { + val interactionSource = remember { MutableInteractionSource() } + val textStyle = TextStyle.Default.copy( + fontFamily = FontFamily.Monospace, + color = MaterialTheme.colorScheme.onSurface + ) + CompositionLocalProvider(LocalTextSelectionColors provides colors.textSelectionColors) { + BasicTextField( + value = textFieldValue, + modifier = Modifier.fillMaxSize(), + onValueChange = setTextFieldAndViewModelValues, + enabled = true, + readOnly = false, + textStyle = textStyle, + cursorBrush = SolidColor(colors.cursorColor), + keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences), + keyboardActions = KeyboardActions.Default, + interactionSource = interactionSource, + singleLine = false, + maxLines = Int.MAX_VALUE, + minLines = 1, + decorationBox = @Composable { innerTextField -> + // places leading icon, text field with label and placeholder, trailing icon + TextFieldDefaults.DecorationBox( + value = textFieldValue.text, + visualTransformation = VisualTransformation.None, + innerTextField = innerTextField, + placeholder = { + Text("Markdown here…") + }, + singleLine = false, + enabled = true, + interactionSource = interactionSource, + colors = colors, + contentPadding = PaddingValues(8.dp) + ) + } + ) + } + } +} + +private fun String.annotate(enableReadability: Boolean): AnnotatedString { + if (!enableReadability) return AnnotatedString(this) + val readability = Readability(this) + val annotated = AnnotatedString.Builder(this) + for (sentence in readability.sentences()) { + var color = Color.Transparent + if (sentence.syllableCount() > 25) color = Color(229, 232, 42, 100) + if (sentence.syllableCount() > 35) color = Color(193, 66, 66, 100) + annotated.addStyle(SpanStyle(background = color), sentence.start(), sentence.end()) + } + return annotated.toAnnotatedString() +} \ No newline at end of file