From d586476e53902408e5e1fd2742434cc9919a8610 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Sun, 20 Feb 2022 18:55:48 -0500 Subject: [PATCH] Adding a markdown helper. Fixes #38 --- .../ui/components/comment/edit/CommentEdit.kt | 20 +- .../comment/edit/CommentEditActivity.kt | 10 +- .../components/comment/reply/CommentReply.kt | 32 +- .../comment/reply/CommentReplyActivity.kt | 32 +- .../jerboa/ui/components/common/AppBars.kt | 2 +- .../jerboa/ui/components/common/TextFields.kt | 533 +++++++++++++++++- .../ui/components/post/create/CreatePost.kt | 1 + .../private_message/PrivateMessageReply.kt | 11 +- .../PrivateMessageReplyActivity.kt | 12 +- .../ui/components/report/CreateReport.kt | 11 +- .../comment/CreateCommentReportActivity.kt | 12 +- .../report/post/CreatePostReportActivity.kt | 12 +- 12 files changed, 576 insertions(+), 112 deletions(-) diff --git a/app/src/main/java/com/jerboa/ui/components/comment/edit/CommentEdit.kt b/app/src/main/java/com/jerboa/ui/components/comment/edit/CommentEdit.kt index e86ac3ac7..b1ac60c21 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/edit/CommentEdit.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/edit/CommentEdit.kt @@ -1,7 +1,6 @@ package com.jerboa.ui.components.comment.edit import android.net.Uri -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.* @@ -10,14 +9,14 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Save import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.TextFieldValue import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.jerboa.datatypes.CommentView -import com.jerboa.ui.components.common.PickImage -import com.jerboa.ui.components.common.ReplyTextField +import com.jerboa.db.Account +import com.jerboa.ui.components.common.MarkdownTextField import com.jerboa.ui.components.common.simpleVerticalScrollbar import com.jerboa.ui.theme.APP_BAR_ELEVATION -import com.jerboa.ui.theme.MEDIUM_PADDING @Composable fun CommentEditHeader( @@ -66,9 +65,10 @@ fun CommentEditHeader( @Composable fun CommentEdit( - content: String, - onContentChange: (String) -> Unit, + content: TextFieldValue, + onContentChange: (TextFieldValue) -> Unit, onPickedImage: (image: Uri) -> Unit, + account: Account?, ) { val listState = rememberLazyListState() @@ -77,13 +77,7 @@ fun CommentEdit( modifier = Modifier.simpleVerticalScrollbar(listState) ) { item { - ReplyTextField(reply = content, onReplyChange = onContentChange) - } - item { - PickImage( - onPickedImage = onPickedImage, - modifier = Modifier.padding(MEDIUM_PADDING) - ) + MarkdownTextField(reply = content, onReplyChange = onContentChange, account = account) } } } diff --git a/app/src/main/java/com/jerboa/ui/components/comment/edit/CommentEditActivity.kt b/app/src/main/java/com/jerboa/ui/components/comment/edit/CommentEditActivity.kt index 5288363ea..8b34e5942 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/edit/CommentEditActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/edit/CommentEditActivity.kt @@ -5,9 +5,9 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Surface import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.TextFieldValue import androidx.navigation.NavController import com.jerboa.api.uploadPictrsImage import com.jerboa.appendMarkdownImage @@ -34,7 +34,8 @@ fun CommentEditActivity( val ctx = LocalContext.current val account = getCurrentAccount(accountViewModel = accountViewModel) val scope = rememberCoroutineScope() - var content by rememberSaveable { mutableStateOf(commentEditViewModel.commentView.value?.comment?.content.orEmpty()) } + + var content by remember { mutableStateOf(TextFieldValue(commentEditViewModel.commentView.value?.comment?.content.orEmpty())) } val focusManager = LocalFocusManager.current @@ -47,7 +48,7 @@ fun CommentEditActivity( onSaveClick = { account?.also { acct -> commentEditViewModel.editComment( - content = content, + content = content.text, ctx = ctx, navController = navController, focusManager = focusManager, @@ -63,6 +64,7 @@ fun CommentEditActivity( content = { CommentEdit( content = content, + account = account, onContentChange = { content = it }, onPickedImage = { uri -> val imageIs = imageInputStreamFromUri(ctx, uri) @@ -70,7 +72,7 @@ fun CommentEditActivity( account?.also { acct -> val url = uploadPictrsImage(acct, imageIs, ctx) url?.also { - content = appendMarkdownImage(content, it) + content = TextFieldValue(appendMarkdownImage(content.text, it)) } } } diff --git a/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReply.kt b/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReply.kt index e60c31591..73ebf8fc1 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReply.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReply.kt @@ -12,15 +12,16 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Send import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.jerboa.datatypes.CommentView import com.jerboa.datatypes.PostView import com.jerboa.datatypes.sampleCommentView +import com.jerboa.db.Account import com.jerboa.ui.components.comment.CommentNodeHeader -import com.jerboa.ui.components.common.PickImage -import com.jerboa.ui.components.common.ReplyTextField +import com.jerboa.ui.components.common.MarkdownTextField import com.jerboa.ui.components.common.simpleVerticalScrollbar import com.jerboa.ui.components.post.PostNodeHeader import com.jerboa.ui.theme.LARGE_PADDING @@ -122,11 +123,12 @@ fun RepliedPost( @Composable fun CommentReply( commentView: CommentView, - reply: String, - onReplyChange: (String) -> Unit, + reply: TextFieldValue, + onReplyChange: (TextFieldValue) -> Unit, onPersonClick: (personId: Int) -> Unit, onPickedImage: (image: Uri) -> Unit, isModerator: Boolean, + account: Account?, ) { val listState = rememberLazyListState() @@ -145,13 +147,7 @@ fun CommentReply( Divider(modifier = Modifier.padding(vertical = LARGE_PADDING)) } item { - ReplyTextField(reply = reply, onReplyChange = onReplyChange) - } - item { - PickImage( - onPickedImage = onPickedImage, - modifier = Modifier.padding(MEDIUM_PADDING) - ) + MarkdownTextField(reply = reply, onReplyChange = onReplyChange, account = account) } } } @@ -159,11 +155,11 @@ fun CommentReply( @Composable fun PostReply( postView: PostView, - reply: String, - onReplyChange: (String) -> Unit, + reply: TextFieldValue, + onReplyChange: (TextFieldValue) -> Unit, onPersonClick: (personId: Int) -> Unit, - onPickedImage: (image: Uri) -> Unit, isModerator: Boolean, + account: Account?, ) { val listState = rememberLazyListState() @@ -182,13 +178,7 @@ fun PostReply( Divider(modifier = Modifier.padding(vertical = LARGE_PADDING)) } item { - ReplyTextField(reply = reply, onReplyChange = onReplyChange) - } - item { - PickImage( - onPickedImage = onPickedImage, - modifier = Modifier.padding(MEDIUM_PADDING) - ) + MarkdownTextField(reply = reply, onReplyChange = onReplyChange, account = account) } } } diff --git a/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReplyActivity.kt b/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReplyActivity.kt index 2a9c64e8d..8b700fb31 100644 --- a/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReplyActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/comment/reply/CommentReplyActivity.kt @@ -7,10 +7,10 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Surface import androidx.compose.runtime.* -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.TextFieldValue import androidx.navigation.NavController import com.jerboa.api.uploadPictrsImage import com.jerboa.appendMarkdownImage @@ -39,7 +39,7 @@ fun CommentReplyActivity( val ctx = LocalContext.current val account = getCurrentAccount(accountViewModel = accountViewModel) val scope = rememberCoroutineScope() - var reply by rememberSaveable { mutableStateOf("") } + var reply by remember { mutableStateOf(TextFieldValue("")) } val focusManager = LocalFocusManager.current @@ -52,7 +52,7 @@ fun CommentReplyActivity( onSendClick = { account?.also { acct -> commentReplyViewModel.createComment( - content = reply, + content = reply.text, account = acct, ctx = ctx, navController = navController, @@ -73,6 +73,7 @@ fun CommentReplyActivity( commentReplyViewModel.commentParentView.value?.also { commentView -> CommentReply( commentView = commentView, + account = account, reply = reply, onReplyChange = { reply = it }, onPersonClick = { personId -> @@ -90,17 +91,27 @@ fun CommentReplyActivity( account?.also { acct -> val url = uploadPictrsImage(acct, imageIs, ctx) url?.also { - reply = appendMarkdownImage(reply, it) + reply = TextFieldValue( + appendMarkdownImage( + reply.text, + it + ) + ) } } } }, - isModerator(commentView.creator, postViewModel.moderators) + isModerator = isModerator( + commentView.creator, + postViewModel + .moderators + ), ) } ?: run { commentReplyViewModel.postView.value?.also { postView -> PostReply( postView = postView, + account = account, reply = reply, onReplyChange = { reply = it }, onPersonClick = { personId -> @@ -112,17 +123,6 @@ fun CommentReplyActivity( ctx ) }, - onPickedImage = { uri -> - val imageIs = imageInputStreamFromUri(ctx, uri) - scope.launch { - account?.also { acct -> - val url = uploadPictrsImage(acct, imageIs, ctx) - url?.also { - reply = appendMarkdownImage(reply, it) - } - } - } - }, isModerator = isModerator(postView.creator, postViewModel.moderators) ) } diff --git a/app/src/main/java/com/jerboa/ui/components/common/AppBars.kt b/app/src/main/java/com/jerboa/ui/components/common/AppBars.kt index ca34dc44a..ec5a35125 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/AppBars.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/AppBars.kt @@ -258,7 +258,7 @@ fun ActionBarButton( } @Composable -fun DotSpacer(padding: Dp = MEDIUM_PADDING) { +fun DotSpacer(padding: Dp = SMALL_PADDING) { Text( text = "ยท", modifier = Modifier.padding(horizontal = padding) diff --git a/app/src/main/java/com/jerboa/ui/components/common/TextFields.kt b/app/src/main/java/com/jerboa/ui/components/common/TextFields.kt index 5b86aa2a1..f881dce5d 100644 --- a/app/src/main/java/com/jerboa/ui/components/common/TextFields.kt +++ b/app/src/main/java/com/jerboa/ui/components/common/TextFields.kt @@ -1,50 +1,174 @@ package com.jerboa.ui.components.common -import androidx.compose.foundation.layout.fillMaxWidth +import android.net.Uri +import android.util.Log +import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text -import androidx.compose.material.TextField -import androidx.compose.material.TextFieldDefaults -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.remember +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.ExperimentalUnitApi import androidx.compose.ui.unit.sp +import com.jerboa.api.uploadPictrsImage +import com.jerboa.appendMarkdownImage +import com.jerboa.db.Account +import com.jerboa.imageInputStreamFromUri +import com.jerboa.ui.theme.MEDIUM_PADDING +import com.jerboa.ui.theme.Muted +import com.jerboa.ui.theme.XXL_PADDING import dev.jeziellago.compose.markdowntext.MarkdownText +import kotlinx.coroutines.launch @Composable -fun ReplyTextField( - reply: String, - onReplyChange: (String) -> Unit +fun MarkdownTextField( + reply: TextFieldValue, + onReplyChange: (TextFieldValue) -> Unit, + account: Account?, ) { val focusRequester = remember { FocusRequester() } + val imageUploading = rememberSaveable { mutableStateOf(false) } + val launcher = imageUploadLauncher(account, onReplyChange, reply, imageUploading) - TextField( - value = reply, - onValueChange = onReplyChange, - placeholder = { Text(text = "Type your comment") }, - modifier = Modifier - .fillMaxWidth() - .focusRequester(focusRequester), - keyboardOptions = KeyboardOptions.Default.copy( - capitalization = KeyboardCapitalization.Sentences, - keyboardType = KeyboardType.Text, - autoCorrect = true, - ), - colors = TextFieldDefaults.textFieldColors( - backgroundColor = Color.Transparent, - focusedIndicatorColor = Color.Transparent, - unfocusedIndicatorColor = Color.Transparent, + var showCreateLink by remember { mutableStateOf(false) } + var showPreview by remember { mutableStateOf(false) } + + if (showCreateLink) { + CreateLinkDialog( + onDismissRequest = { showCreateLink = false }, + onClickOk = { + showCreateLink = false + val out = reply.text.insert(reply.selection.start, it) + val cursor = TextRange(reply.selection.start + it.length) + onReplyChange(TextFieldValue(out, cursor)) + }, ) - ) + } + + if (showPreview) { + ShowPreviewDialog( + content = reply.text, + onDismissRequest = { showPreview = false }, + ) + } + + Column { + TextField( + value = reply, + onValueChange = onReplyChange, + placeholder = { Text(text = "Type your comment") }, + modifier = Modifier + .fillMaxWidth() + .focusRequester(focusRequester), + keyboardOptions = KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.Sentences, + keyboardType = KeyboardType.Text, + autoCorrect = true, + ), + colors = TextFieldDefaults.textFieldColors( + textColor = MaterialTheme.colors.onSurface, + backgroundColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + ) + ) + MarkdownHelperBar( + imageUploading = imageUploading.value, + onBoldClick = { + simpleMarkdownSurround( + "**", + value = reply, + onValueChange = onReplyChange + ) + }, + onItalicsClick = { + simpleMarkdownSurround( + "*", + value = reply, + onValueChange = onReplyChange + ) + }, + onQuoteClick = { + simpleMarkdownSurround( + "> ", + value = reply, + onValueChange = onReplyChange, + surround = false, + ) + }, + onHeaderClick = { + simpleMarkdownSurround( + "# ", + value = reply, + onValueChange = onReplyChange, + surround = false, + ) + }, + onCodeClick = { + simpleMarkdownSurround( + "`", + value = reply, + onValueChange = onReplyChange + ) + }, + onStrikethroughClick = { + simpleMarkdownSurround( + "~~", + value = reply, + onValueChange = onReplyChange + ) + }, + onSubscriptClick = { + simpleMarkdownSurround( + "~", + value = reply, + onValueChange = onReplyChange + ) + }, + onSuperscriptClick = { + simpleMarkdownSurround( + "^", + value = reply, + onValueChange = onReplyChange + ) + }, + onListClick = { + simpleMarkdownSurround( + "- ", + value = reply, + onValueChange = onReplyChange, + surround = false, + ) + }, + onImageClick = { + launcher.launch("image/*") + }, + onLinkClick = { + showCreateLink = true + }, + onPreviewClick = { + showPreview = true + } + ) + } DisposableEffect(Unit) { focusRequester.requestFocus() @@ -52,6 +176,355 @@ fun ReplyTextField( } } +@Composable +fun CreateLinkDialog( + onClickOk: (String) -> Unit, + onDismissRequest: () -> Unit, +) { + + var text by rememberSaveable { mutableStateOf("") } + var link by rememberSaveable { mutableStateOf("") } + + AlertDialog( + onDismissRequest = onDismissRequest, + text = { + Column( + modifier = Modifier + .padding(MEDIUM_PADDING) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(MEDIUM_PADDING) + ) { + Text( + text = "Insert link", + style = MaterialTheme.typography.h6, + color = MaterialTheme.colors.onSurface, + ) + OutlinedTextField( + value = text, + onValueChange = { text = it }, + label = { + Text(text = "Text") + }, + modifier = Modifier.fillMaxWidth(), + ) + OutlinedTextField( + value = link, + onValueChange = { link = it }, + label = { + Text(text = "Link") + }, + modifier = Modifier.fillMaxWidth(), + ) + } + }, + buttons = { + Row( + horizontalArrangement = Arrangement.End, + modifier = Modifier.padding(horizontal = XXL_PADDING).fillMaxWidth(), + ) { + TextButton( + onClick = onDismissRequest, + ) { + Text( + text = "Cancel", + color = Muted, + ) + } + TextButton( + onClick = { + onClickOk("[$text]($link)") + }, + ) { + Text( + text = "OK", + ) + } + } + } + ) +} + +@Composable +fun ShowPreviewDialog( + content: String, + onDismissRequest: () -> Unit, +) { + AlertDialog( + onDismissRequest = onDismissRequest, + text = { + Column( + modifier = Modifier.verticalScroll(rememberScrollState()) + ) { + MyMarkdownText( + markdown = content, + ) + } + }, + buttons = { + Row( + horizontalArrangement = Arrangement.End, + modifier = Modifier.padding(horizontal = XXL_PADDING).fillMaxWidth(), + ) { + TextButton( + onClick = onDismissRequest, + ) { + Text( + text = "OK", + color = Muted, + ) + } + } + } + ) +} + +@Preview +@Composable +fun CreateLinkDialogPreview() { + CreateLinkDialog(onClickOk = {}, onDismissRequest = {}) +} + +@Composable +private fun imageUploadLauncher( + account: Account?, + onReplyChange: (TextFieldValue) -> Unit, + reply: TextFieldValue, + imageUploading: MutableState, +): ManagedActivityResultLauncher { + val ctx = LocalContext.current + val scope = rememberCoroutineScope() + + // Some image upload reqs + var imageUri by remember { + mutableStateOf(null) + } + + val launcher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.GetContent() + ) { uri: Uri? -> + imageUri = uri + Log.d("jerboa", imageUri.toString()) + + uri?.also { cUri -> + imageUploading.value = true + val imageIs = imageInputStreamFromUri(ctx, cUri) + scope.launch { + account?.also { acct -> + val url = uploadPictrsImage(acct, imageIs, ctx) + url?.also { + imageUploading.value = false + onReplyChange(TextFieldValue(appendMarkdownImage(reply.text, it))) + } + } + } + } + } + return launcher +} + +fun simpleMarkdownSurround( + markdownChar: String, + value: TextFieldValue, + onValueChange: (TextFieldValue) -> Unit, + surround: Boolean = true, +) { + val out = if (value.selection.start == value.selection.end) { + var altered = value.text.insert(value.selection.start, markdownChar) + if (surround) { + altered = altered.insert(value.selection.start, markdownChar) + } + val cursor = TextRange(value.selection.start + markdownChar.length) + + TextFieldValue(altered, cursor) + } else { + var altered = value.text + .insert(value.selection.start, markdownChar) + if (surround) { + altered = altered + .insert(value.selection.end + markdownChar.length, markdownChar) + } +// Log.d("jerboa", "start = ${value.selection.start}, end = ${value.selection.end}") + + // TODO weird glitch when its the last item + val start = value.selection.start + markdownChar.length + val end = if (surround) { + value.selection.end + markdownChar.length + } else { + value + .selection.end + } + val cursor = if (value.selection.end == value.text.length) { + TextRange(start) + } else { + TextRange( + start, + end, + ) + } + TextFieldValue(altered, cursor) + } + + onValueChange(out) +} + +@Composable +fun MarkdownHelperBar( + onPreviewClick: () -> Unit, + onHeaderClick: () -> Unit, + onImageClick: () -> Unit, + onLinkClick: () -> Unit, + onListClick: () -> Unit, + onQuoteClick: () -> Unit, + onBoldClick: () -> Unit, + onItalicsClick: () -> Unit, + onCodeClick: () -> Unit, + onStrikethroughClick: () -> Unit, + onSubscriptClick: () -> Unit, + onSuperscriptClick: () -> Unit, + imageUploading: Boolean +) { + + Row( + modifier = Modifier.horizontalScroll(rememberScrollState()) + ) { + IconButton( + onClick = onPreviewClick, + ) { + Icon( + imageVector = Icons.Default.Preview, + contentDescription = "TODO", + tint = Muted, + ) + } + IconButton( + onClick = onLinkClick, + ) { + Icon( + imageVector = Icons.Default.Link, + contentDescription = "TODO", + tint = Muted, + ) + } + IconButton( + onClick = onImageClick, + enabled = !imageUploading + ) { + if (imageUploading) { + CircularProgressIndicator( + color = MaterialTheme.colors.onSurface + ) + } else { + Icon( + imageVector = Icons.Default.Image, + contentDescription = "TODO", + tint = Muted, + ) + } + } + IconButton( + onClick = onBoldClick, + ) { + Icon( + imageVector = Icons.Default.FormatBold, + contentDescription = "TODO", + tint = Muted, + ) + } + IconButton( + onClick = onItalicsClick, + ) { + Icon( + imageVector = Icons.Default.FormatItalic, + contentDescription = "TODO", + tint = Muted, + ) + } + IconButton( + onClick = onQuoteClick, + ) { + Icon( + imageVector = Icons.Default.FormatQuote, + contentDescription = "TODO", + tint = Muted, + ) + } + IconButton( + onClick = onListClick, + ) { + Icon( + imageVector = Icons.Default.FormatListBulleted, + contentDescription = "TODO", + tint = Muted, + ) + } + IconButton( + onClick = onHeaderClick, + ) { + Icon( + imageVector = Icons.Default.Title, + contentDescription = "TODO", + tint = Muted, + ) + } + IconButton( + onClick = onCodeClick, + ) { + Icon( + imageVector = Icons.Default.Code, + contentDescription = "TODO", + tint = Muted, + ) + } + IconButton( + onClick = onStrikethroughClick, + ) { + Icon( + imageVector = Icons.Default.FormatStrikethrough, + contentDescription = "TODO", + tint = Muted, + ) + } + IconButton( + onClick = onSubscriptClick, + ) { + Icon( + imageVector = Icons.Default.Subscript, + contentDescription = "TODO", + tint = Muted, + ) + } + IconButton( + onClick = onSuperscriptClick, + ) { + Icon( + imageVector = Icons.Default.Superscript, + contentDescription = "TODO", + tint = Muted, + ) + } + } +} + +@Preview +@Composable +fun ReplyMarkdownBarPreview() { + MarkdownHelperBar( + onHeaderClick = {}, + onPreviewClick = {}, + onImageClick = {}, + onListClick = {}, + onQuoteClick = {}, + onBoldClick = {}, + onItalicsClick = {}, + onCodeClick = {}, + onStrikethroughClick = {}, + onSubscriptClick = {}, + onSuperscriptClick = {}, + onLinkClick = {}, + imageUploading = false, + ) +} + @Composable fun PreviewLines( text: String, @@ -85,3 +558,7 @@ fun MyMarkdownText( color = color, ) } + +fun String.insert(index: Int, string: String): String { + return this.substring(0, index) + string + this.substring(index, this.length) +} diff --git a/app/src/main/java/com/jerboa/ui/components/post/create/CreatePost.kt b/app/src/main/java/com/jerboa/ui/components/post/create/CreatePost.kt index b602031da..e017e76ef 100644 --- a/app/src/main/java/com/jerboa/ui/components/post/create/CreatePost.kt +++ b/app/src/main/java/com/jerboa/ui/components/post/create/CreatePost.kt @@ -149,6 +149,7 @@ fun CreatePostBody( onPickedImage = onPickedImage, ) } + // TODO change this to reply text field at some point item { OutlinedTextField( label = { diff --git a/app/src/main/java/com/jerboa/ui/components/private_message/PrivateMessageReply.kt b/app/src/main/java/com/jerboa/ui/components/private_message/PrivateMessageReply.kt index 73107801a..83853b770 100644 --- a/app/src/main/java/com/jerboa/ui/components/private_message/PrivateMessageReply.kt +++ b/app/src/main/java/com/jerboa/ui/components/private_message/PrivateMessageReply.kt @@ -11,12 +11,14 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Send import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.jerboa.datatypes.PrivateMessageView import com.jerboa.datatypes.samplePrivateMessageView -import com.jerboa.ui.components.common.ReplyTextField +import com.jerboa.db.Account +import com.jerboa.ui.components.common.MarkdownTextField import com.jerboa.ui.components.common.simpleVerticalScrollbar import com.jerboa.ui.theme.APP_BAR_ELEVATION import com.jerboa.ui.theme.LARGE_PADDING @@ -96,9 +98,10 @@ fun RepliedPrivateMessagePreview() { @Composable fun PrivateMessageReply( privateMessageView: PrivateMessageView, - reply: String, - onReplyChange: (String) -> Unit, + reply: TextFieldValue, + onReplyChange: (TextFieldValue) -> Unit, onPersonClick: (personId: Int) -> Unit, + account: Account?, ) { val listState = rememberLazyListState() @@ -116,7 +119,7 @@ fun PrivateMessageReply( Divider(modifier = Modifier.padding(vertical = LARGE_PADDING)) } item { - ReplyTextField(reply = reply, onReplyChange = onReplyChange) + MarkdownTextField(reply = reply, onReplyChange = onReplyChange, account = account) } } } diff --git a/app/src/main/java/com/jerboa/ui/components/private_message/PrivateMessageReplyActivity.kt b/app/src/main/java/com/jerboa/ui/components/private_message/PrivateMessageReplyActivity.kt index 2b4c43955..3ae57416c 100644 --- a/app/src/main/java/com/jerboa/ui/components/private_message/PrivateMessageReplyActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/private_message/PrivateMessageReplyActivity.kt @@ -6,14 +6,11 @@ import androidx.compose.material.LinearProgressIndicator import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Surface -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.TextFieldValue import androidx.navigation.NavController import com.jerboa.datatypes.api.CreatePrivateMessage import com.jerboa.db.AccountViewModel @@ -35,7 +32,7 @@ fun PrivateMessageReplyActivity( val ctx = LocalContext.current val account = getCurrentAccount(accountViewModel = accountViewModel) - var reply by rememberSaveable { mutableStateOf("") } + var reply by remember { mutableStateOf(TextFieldValue("")) } val focusManager = LocalFocusManager.current @@ -51,7 +48,7 @@ fun PrivateMessageReplyActivity( val recipientId = privateMessageView.creator.id val form = CreatePrivateMessage( - content = reply, + content = reply.text, recipient_id = recipientId, auth = account.jwt ) @@ -72,6 +69,7 @@ fun PrivateMessageReplyActivity( inboxViewModel.replyToPrivateMessageView?.also { privateMessageView -> PrivateMessageReply( privateMessageView = privateMessageView, + account = account, reply = reply, onReplyChange = { reply = it }, onPersonClick = { personId -> diff --git a/app/src/main/java/com/jerboa/ui/components/report/CreateReport.kt b/app/src/main/java/com/jerboa/ui/components/report/CreateReport.kt index c4fe89888..30d90618c 100644 --- a/app/src/main/java/com/jerboa/ui/components/report/CreateReport.kt +++ b/app/src/main/java/com/jerboa/ui/components/report/CreateReport.kt @@ -8,9 +8,11 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Send import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.TextFieldValue import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController -import com.jerboa.ui.components.common.ReplyTextField +import com.jerboa.db.Account +import com.jerboa.ui.components.common.MarkdownTextField import com.jerboa.ui.components.common.simpleVerticalScrollbar import com.jerboa.ui.theme.APP_BAR_ELEVATION @@ -61,8 +63,9 @@ fun CreateReportHeader( @Composable fun CreateReportBody( - reason: String, - onReasonChange: (String) -> Unit, + reason: TextFieldValue, + onReasonChange: (TextFieldValue) -> Unit, + account: Account?, ) { val listState = rememberLazyListState() @@ -71,7 +74,7 @@ fun CreateReportBody( modifier = Modifier.simpleVerticalScrollbar(listState) ) { item { - ReplyTextField(reply = reason, onReplyChange = onReasonChange) + MarkdownTextField(reply = reason, onReplyChange = onReasonChange, account = account) } } } diff --git a/app/src/main/java/com/jerboa/ui/components/report/comment/CreateCommentReportActivity.kt b/app/src/main/java/com/jerboa/ui/components/report/comment/CreateCommentReportActivity.kt index d877840f0..70bef664b 100644 --- a/app/src/main/java/com/jerboa/ui/components/report/comment/CreateCommentReportActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/report/comment/CreateCommentReportActivity.kt @@ -4,13 +4,10 @@ import android.util.Log import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Surface -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.TextFieldValue import androidx.navigation.NavController import com.jerboa.db.AccountViewModel import com.jerboa.ui.components.common.getCurrentAccount @@ -29,7 +26,7 @@ fun CreateCommentReportActivity( val ctx = LocalContext.current val account = getCurrentAccount(accountViewModel = accountViewModel) - var reason by rememberSaveable { mutableStateOf("") } + var reason by remember { mutableStateOf(TextFieldValue("")) } val focusManager = LocalFocusManager.current @@ -42,7 +39,7 @@ fun CreateCommentReportActivity( onCreateClick = { account?.also { acct -> createReportViewModel.createCommentReport( - reason = reason, + reason = reason.text, ctx = ctx, navController = navController, focusManager = focusManager, @@ -56,6 +53,7 @@ fun CreateCommentReportActivity( CreateReportBody( reason = reason, onReasonChange = { reason = it }, + account = account, ) } ) diff --git a/app/src/main/java/com/jerboa/ui/components/report/post/CreatePostReportActivity.kt b/app/src/main/java/com/jerboa/ui/components/report/post/CreatePostReportActivity.kt index 1a9fc79b7..329330adb 100644 --- a/app/src/main/java/com/jerboa/ui/components/report/post/CreatePostReportActivity.kt +++ b/app/src/main/java/com/jerboa/ui/components/report/post/CreatePostReportActivity.kt @@ -4,13 +4,10 @@ import android.util.Log import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Surface -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.TextFieldValue import androidx.navigation.NavController import com.jerboa.db.AccountViewModel import com.jerboa.ui.components.common.getCurrentAccount @@ -29,7 +26,7 @@ fun CreatePostReportActivity( val ctx = LocalContext.current val account = getCurrentAccount(accountViewModel = accountViewModel) - var reason by rememberSaveable { mutableStateOf("") } + var reason by remember { mutableStateOf(TextFieldValue("")) } val focusManager = LocalFocusManager.current @@ -42,7 +39,7 @@ fun CreatePostReportActivity( onCreateClick = { account?.also { acct -> createReportViewModel.createPostReport( - reason = reason, + reason = reason.text, ctx = ctx, navController = navController, focusManager = focusManager, @@ -56,6 +53,7 @@ fun CreatePostReportActivity( CreateReportBody( reason = reason, onReasonChange = { reason = it }, + account = account, ) } )