From 8ff4ac3709a5088638981b6307899541439a4bd0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 22 Feb 2024 09:54:21 -0500 Subject: [PATCH] Inserts uploaded URLs where the cursor is and not at the end of the file. --- .../amethyst/ui/actions/NewPostViewModel.kt | 10 +- .../amethyst/ui/note/BlankNote.kt | 3 +- .../commons/TextFieldValueExtensionTest.kt | 121 ++++++++++++++++++ .../commons/TextFieldValueExtensions.kt | 43 +++++++ 4 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 commons/src/androidTest/java/com/vitorpamplona/amethyst/commons/TextFieldValueExtensionTest.kt create mode 100644 commons/src/main/java/com/vitorpamplona/amethyst/commons/TextFieldValueExtensions.kt diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index f8ee6072e..f25bdeb94 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -36,6 +36,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.fonfon.kgeohash.toGeoHash import com.vitorpamplona.amethyst.commons.RichTextParser +import com.vitorpamplona.amethyst.commons.insertUrlAtCursor import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note @@ -736,10 +737,7 @@ open class NewPostViewModel() : ViewModel() { isUploadingImage = false nip94attachments = nip94attachments + event - message = - TextFieldValue( - message.text + "\n" + imageUrl, - ) + message = message.insertUrlAtCursor(imageUrl) urlPreview = findUrlInMessage() } }, @@ -776,7 +774,9 @@ open class NewPostViewModel() : ViewModel() { isUploadingImage = false - note?.let { message = TextFieldValue(message.text + "\nnostr:" + it.toNEvent()) } + note?.let { + message = message.insertUrlAtCursor("nostr:" + it.toNEvent()) + } urlPreview = findUrlInMessage() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt index 3511ff2fd..81823b0c9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt @@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Divider +import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -74,7 +75,7 @@ fun BlankNote( } if (!showDivider) { - Divider( + HorizontalDivider( modifier = Modifier.padding(vertical = 10.dp), thickness = DividerThickness, ) diff --git a/commons/src/androidTest/java/com/vitorpamplona/amethyst/commons/TextFieldValueExtensionTest.kt b/commons/src/androidTest/java/com/vitorpamplona/amethyst/commons/TextFieldValueExtensionTest.kt new file mode 100644 index 000000000..1c8174eab --- /dev/null +++ b/commons/src/androidTest/java/com/vitorpamplona/amethyst/commons/TextFieldValueExtensionTest.kt @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons + +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.TextFieldValue +import androidx.test.ext.junit.runners.AndroidJUnit4 +import junit.framework.TestCase.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TextFieldValueExtensionTest { + @Test + fun testInsertTwoCharsStart() { + val current = TextFieldValue("ab", selection = TextRange(0, 0)) + val next = current.insertUrlAtCursor("http://a.b") + + assertEquals("http://a.b ab", next.text) + assertEquals(TextRange(10, 10), next.selection) + } + + @Test + fun testInsertTwoCharsMiddle() { + val current = TextFieldValue("ab", selection = TextRange(1, 1)) + val next = current.insertUrlAtCursor("http://a.b") + + assertEquals("a http://a.b b", next.text) + assertEquals(TextRange(12, 12), next.selection) + } + + @Test + fun testInsertTwoCharsEnd() { + val current = TextFieldValue("ab", selection = TextRange(2, 2)) + val next = current.insertUrlAtCursor("http://a.b") + + assertEquals("ab http://a.b", next.text) + assertEquals(TextRange(13, 13), next.selection) + } + + @Test + fun testInsertOneCharStart() { + val current = TextFieldValue("a", selection = TextRange(0, 0)) + val next = current.insertUrlAtCursor("http://a.b") + + assertEquals("http://a.b a", next.text) + assertEquals(TextRange(10, 10), next.selection) + } + + @Test + fun testInsertOneCharEnd() { + val current = TextFieldValue("a", selection = TextRange(1, 1)) + val next = current.insertUrlAtCursor("http://a.b") + + assertEquals("a http://a.b", next.text) + assertEquals(TextRange(12, 12), next.selection) + } + + @Test + fun testInsertTwoCharsWithSpaceStart() { + val current = TextFieldValue("a b", selection = TextRange(1, 1)) + val next = current.insertUrlAtCursor("http://a.b") + + assertEquals("a http://a.b b", next.text) + assertEquals(TextRange(12, 12), next.selection) + } + + @Test + fun testInsertTwoCharsWithSpaceEnd() { + val current = TextFieldValue("a b", selection = TextRange(2, 2)) + val next = current.insertUrlAtCursor("http://a.b") + + assertEquals("a http://a.b b", next.text) + assertEquals(TextRange(12, 12), next.selection) + } + + @Test + fun testInsertTwoCharsWithThreeSpaceStart() { + val current = TextFieldValue("a b", selection = TextRange(1, 1)) + val next = current.insertUrlAtCursor("http://a.b") + + assertEquals("a http://a.b b", next.text) + assertEquals(TextRange(12, 12), next.selection) + } + + @Test + fun testInsertTwoCharsWithThreeSpaceMiddle() { + val current = TextFieldValue("a b", selection = TextRange(2, 2)) + val next = current.insertUrlAtCursor("http://a.b") + + assertEquals("a http://a.b b", next.text) + assertEquals(TextRange(12, 12), next.selection) + } + + @Test + fun testInsertTwoCharsWithThreeSpaceEnd() { + val current = TextFieldValue("a b", selection = TextRange(3, 3)) + val next = current.insertUrlAtCursor("http://a.b") + + assertEquals("a http://a.b b", next.text) + assertEquals(TextRange(13, 13), next.selection) + } +} diff --git a/commons/src/main/java/com/vitorpamplona/amethyst/commons/TextFieldValueExtensions.kt b/commons/src/main/java/com/vitorpamplona/amethyst/commons/TextFieldValueExtensions.kt new file mode 100644 index 000000000..76b91b312 --- /dev/null +++ b/commons/src/main/java/com/vitorpamplona/amethyst/commons/TextFieldValueExtensions.kt @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons + +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.input.TextFieldValue + +fun TextFieldValue.insertUrlAtCursor(url: String): TextFieldValue { + var toInsert = url + if (selection.start > 0 && text[selection.start - 1] != ' ') { + toInsert = " $toInsert" + } + + // takes the position before adding an empty char after the url + val endOfUrlIndex = selection.start + toInsert.length + + if (selection.end < text.length && text[selection.end] != ' ') { + toInsert = "$toInsert " + } + + return TextFieldValue( + text.replaceRange(selection.start, selection.end, toInsert), + TextRange(endOfUrlIndex, endOfUrlIndex), + ) +}