refactor: migrate UrlUserTagTransformation to UrlUserTagOutputTransformation
The legacy VisualTransformation paired with TextField(value, onValueChange)
goes through Compose Foundation's LegacyCursorAnchorInfoBuilder, which
throws "endOffset must be greater than startOffset" when the IME has
requested CURSOR_UPDATE_MONITOR and the field's visible bounds momentarily
collapse to zero width. The OutputTransformation API paired with
BasicTextField(state: TextFieldState) routes through a different,
non-legacy cursor anchor info controller and does not hit that bug.
Migrate the two remaining VisualTransformation call sites
(EditPostView.MessageField and ForwardZapTo) to the OutputTransformation
+ TextFieldState path. This requires:
- IZapField: forwardZapToEditting becomes TextFieldState; the
TextFieldValue-shaped updateZapForwardTo() callback is replaced by
onForwardZapTextChanged() which reads the current state.
- 8 ViewModels (ChannelNewMessage, ChatNewMessage, NestNewMessage,
NewProduct, LongFormPost, NewPublicMessage, ShortNotePost,
CommentPost): clear via clearText() instead of reassigning a new
TextFieldValue; rewrite onForwardZapTextChanged() to read from
forwardZapToEditting directly.
- ForwardZapTo: switch from OutlinedTextField to ThinPaddingTextField
with MentionPreservingInputTransformation +
UrlUserTagOutputTransformation.
- EditPostViewModel: message becomes TextFieldState; updateMessage()
becomes onMessageChanged() (the state updates itself); the in-place
replaceCurrentWord/insertUrlAtCursor TextFieldState overloads
replace the value-based ones.
- EditPostView: MessageField now uses ThinPaddingTextField with the
OutputTransformation. Also drops a stale commented-out subject row
that still referenced the legacy transformation.
- Delete UrlUserTagTransformation and its androidTest (no callers
remain).
- Remove MainThreadCrashGuard and its hook in Amethyst.onCreate: the
legacy cursor-anchor-info crash surface is gone with the remaining
mention TextFields, so the framework workaround is no longer
earning its keep.
This commit is contained in:
-225
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.ui.actions.buildAnnotatedStringWithUrlHighlighting
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
||||
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKey
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class UrlUserTagTransformationTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.vitorpamplona.amethyst", appContext.packageName.removeSuffix(".debug"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testKeepTransformedIndexFullyInsideTransformedText() {
|
||||
val user =
|
||||
LocalCache.getOrCreateUser(
|
||||
decodePublicKey("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z")
|
||||
.toHexKey(),
|
||||
)
|
||||
user.metadata().newMetadata(
|
||||
UserMetadata().also {
|
||||
it.displayName = "Vitor Pamplona"
|
||||
},
|
||||
MetadataEvent(
|
||||
id = "",
|
||||
pubKey = "",
|
||||
createdAt = 0,
|
||||
tags = emptyArray(),
|
||||
content = "",
|
||||
sig = "",
|
||||
),
|
||||
)
|
||||
|
||||
val original = "@npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z"
|
||||
|
||||
val transformedText =
|
||||
buildAnnotatedStringWithUrlHighlighting(
|
||||
AnnotatedString(original),
|
||||
Color.Red,
|
||||
)
|
||||
|
||||
val expected = "@Vitor Pamplona"
|
||||
assertEquals(expected, transformedText.text.text)
|
||||
|
||||
// The mention is treated as an atomic wedge: any cursor strictly inside the
|
||||
// underlying npub snaps to the trailing edge of the displayed "@Vitor Pamplona"
|
||||
// (and vice versa). This prevents an IME from placing the cursor in the middle
|
||||
// of the bech32 and corrupting it on backspace.
|
||||
assertEquals(0, transformedText.offsetMapping.originalToTransformed(0))
|
||||
for (i in 1..63) {
|
||||
assertEquals("originalToTransformed($i)", 15, transformedText.offsetMapping.originalToTransformed(i))
|
||||
}
|
||||
assertEquals(15, transformedText.offsetMapping.originalToTransformed(64))
|
||||
|
||||
assertEquals(0, transformedText.offsetMapping.transformedToOriginal(0))
|
||||
for (i in 1..14) {
|
||||
assertEquals("transformedToOriginal($i)", 64, transformedText.offsetMapping.transformedToOriginal(i))
|
||||
}
|
||||
assertEquals(64, transformedText.offsetMapping.transformedToOriginal(15))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun transformationText() {
|
||||
val user =
|
||||
LocalCache.getOrCreateUser(
|
||||
decodePublicKey("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z")
|
||||
.toHexKey(),
|
||||
)
|
||||
user.metadata().newMetadata(
|
||||
UserMetadata().also {
|
||||
it.displayName = "Vitor Pamplona"
|
||||
},
|
||||
MetadataEvent(
|
||||
id = "",
|
||||
pubKey = "",
|
||||
createdAt = 0,
|
||||
tags = emptyArray(),
|
||||
content = "",
|
||||
sig = "",
|
||||
),
|
||||
)
|
||||
|
||||
val transformedText =
|
||||
buildAnnotatedStringWithUrlHighlighting(
|
||||
AnnotatedString("New Hey @npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z"),
|
||||
Color.Red,
|
||||
)
|
||||
|
||||
assertEquals("New Hey @Vitor Pamplona", transformedText.text.text)
|
||||
|
||||
// Outside the wedge: identity mapping.
|
||||
assertEquals(0, transformedText.offsetMapping.originalToTransformed(0)) // Before N
|
||||
assertEquals(4, transformedText.offsetMapping.originalToTransformed(4)) // Before H
|
||||
assertEquals(8, transformedText.offsetMapping.originalToTransformed(8)) // Before @ (boundary)
|
||||
|
||||
// Strictly inside the underlying npub: snaps to the end of "@Vitor Pamplona" (offset 23).
|
||||
assertEquals(23, transformedText.offsetMapping.originalToTransformed(9)) // Before n
|
||||
assertEquals(23, transformedText.offsetMapping.originalToTransformed(12)) // Before b
|
||||
assertEquals(23, transformedText.offsetMapping.originalToTransformed(13)) // Before 1
|
||||
assertEquals(23, transformedText.offsetMapping.originalToTransformed(71)) // Before z
|
||||
|
||||
// End-of-wedge boundary maps to end of displayed mention.
|
||||
assertEquals(23, transformedText.offsetMapping.originalToTransformed(72))
|
||||
|
||||
// Outside the wedge in displayed: identity.
|
||||
assertEquals(0, transformedText.offsetMapping.transformedToOriginal(0))
|
||||
assertEquals(4, transformedText.offsetMapping.transformedToOriginal(4))
|
||||
assertEquals(8, transformedText.offsetMapping.transformedToOriginal(8)) // Before @ (boundary)
|
||||
|
||||
// Strictly inside displayed "@Vitor Pamplona": snaps to end of underlying npub (offset 72).
|
||||
assertEquals(72, transformedText.offsetMapping.transformedToOriginal(9))
|
||||
assertEquals(72, transformedText.offsetMapping.transformedToOriginal(22))
|
||||
|
||||
// End-of-wedge boundary maps to end of underlying mention; past it shifts by deltas.
|
||||
assertEquals(72, transformedText.offsetMapping.transformedToOriginal(23))
|
||||
assertEquals(73, transformedText.offsetMapping.transformedToOriginal(24))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun transformationTextTwoKeys() {
|
||||
val user =
|
||||
LocalCache.getOrCreateUser(
|
||||
decodePublicKey("npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z")
|
||||
.toHexKey(),
|
||||
)
|
||||
|
||||
user.metadata().newMetadata(
|
||||
UserMetadata().also {
|
||||
it.displayName = "Vitor Pamplona"
|
||||
},
|
||||
MetadataEvent(
|
||||
id = "",
|
||||
pubKey = "",
|
||||
createdAt = 0,
|
||||
tags = emptyArray(),
|
||||
content = "",
|
||||
sig = "",
|
||||
),
|
||||
)
|
||||
|
||||
val transformedText =
|
||||
buildAnnotatedStringWithUrlHighlighting(
|
||||
AnnotatedString(
|
||||
"New Hey @npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z and @npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z",
|
||||
),
|
||||
Color.Red,
|
||||
)
|
||||
|
||||
assertEquals("New Hey @Vitor Pamplona and @Vitor Pamplona", transformedText.text.text)
|
||||
|
||||
// Strictly inside the first underlying npub [8, 72): snap to end of first
|
||||
// displayed "@Vitor Pamplona" (offset 23).
|
||||
assertEquals(23, transformedText.offsetMapping.originalToTransformed(11))
|
||||
assertEquals(23, transformedText.offsetMapping.originalToTransformed(12))
|
||||
assertEquals(23, transformedText.offsetMapping.originalToTransformed(13))
|
||||
assertEquals(23, transformedText.offsetMapping.originalToTransformed(70))
|
||||
assertEquals(23, transformedText.offsetMapping.originalToTransformed(71))
|
||||
|
||||
// Boundary at end of first wedge: end of first displayed mention.
|
||||
assertEquals(23, transformedText.offsetMapping.originalToTransformed(72)) // Before <space>
|
||||
assertEquals(24, transformedText.offsetMapping.originalToTransformed(73)) // Before a
|
||||
assertEquals(25, transformedText.offsetMapping.originalToTransformed(74)) // Before n
|
||||
assertEquals(26, transformedText.offsetMapping.originalToTransformed(75)) // Before d
|
||||
assertEquals(27, transformedText.offsetMapping.originalToTransformed(76)) // Before <space>
|
||||
assertEquals(28, transformedText.offsetMapping.originalToTransformed(77)) // Before @ (boundary, second wedge)
|
||||
|
||||
// Strictly inside the second underlying npub [77, 141): snap to end of second
|
||||
// displayed "@Vitor Pamplona" (offset 43).
|
||||
assertEquals(43, transformedText.offsetMapping.originalToTransformed(78)) // Before n
|
||||
assertEquals(43, transformedText.offsetMapping.originalToTransformed(140))
|
||||
|
||||
// Strictly inside first displayed "@Vitor Pamplona" [8, 23): snap to end of
|
||||
// first underlying npub (offset 72).
|
||||
assertEquals(72, transformedText.offsetMapping.transformedToOriginal(22)) // Before a (display)
|
||||
assertEquals(72, transformedText.offsetMapping.transformedToOriginal(23)) // Before <space>
|
||||
assertEquals(73, transformedText.offsetMapping.transformedToOriginal(24)) // Before a
|
||||
assertEquals(74, transformedText.offsetMapping.transformedToOriginal(25)) // Before n
|
||||
assertEquals(75, transformedText.offsetMapping.transformedToOriginal(26)) // Before d
|
||||
assertEquals(76, transformedText.offsetMapping.transformedToOriginal(27)) // Before <space>
|
||||
assertEquals(77, transformedText.offsetMapping.transformedToOriginal(28)) // Before @ (boundary, second wedge)
|
||||
|
||||
// Strictly inside second displayed "@Vitor Pamplona" [28, 43): snap to end of
|
||||
// second underlying npub (offset 141).
|
||||
assertEquals(141, transformedText.offsetMapping.transformedToOriginal(29))
|
||||
assertEquals(141, transformedText.offsetMapping.transformedToOriginal(42))
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@
|
||||
package com.vitorpamplona.amethyst
|
||||
|
||||
import android.app.Application
|
||||
import com.vitorpamplona.amethyst.service.crashreports.MainThreadCrashGuard
|
||||
import com.vitorpamplona.amethyst.service.logging.Logging
|
||||
import com.vitorpamplona.amethyst.service.nests.AppForegroundRecycleHook
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
@@ -41,12 +40,6 @@ class Amethyst : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d("AmethystApp") { "onCreate $this" }
|
||||
|
||||
// Wrap the main thread's Looper.loop() so we can survive a narrow set
|
||||
// of known Compose framework crashes (currently: the LegacyCursorAnchorInfo
|
||||
// IllegalArgumentException). Must be installed before any UI is shown.
|
||||
MainThreadCrashGuard.install()
|
||||
|
||||
instance = AppModules(this)
|
||||
|
||||
// After-background foreground recycle: when the app returns to
|
||||
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.service.crashreports
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
|
||||
/**
|
||||
* Wraps the main thread's `Looper.loop()` so a narrow set of known, benign
|
||||
* framework crashes can be swallowed instead of taking the whole process down.
|
||||
*
|
||||
* Only one case is handled today: `IllegalArgumentException("endOffset must be
|
||||
* greater than startOffset")` thrown from
|
||||
* `androidx.compose.foundation.text.input.internal.LegacyCursorAnchorInfoBuilder`.
|
||||
* It fires when the IME has requested `CURSOR_UPDATE_MONITOR` on a TextField
|
||||
* whose visible bounds momentarily collapse to a zero-width region (during a
|
||||
* layout transition or sheet animation): `addCharacterBounds` computes
|
||||
* `startOffset == endOffset` and `MultiParagraph.fillBoundingBoxes` rejects
|
||||
* the empty range. The IME just misses a cursor update — the app does not
|
||||
* need to die.
|
||||
*
|
||||
* Any other Throwable propagates so the default uncaught-exception handler
|
||||
* (and our crash report saver) still see it.
|
||||
*/
|
||||
object MainThreadCrashGuard {
|
||||
fun install() {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
while (true) {
|
||||
try {
|
||||
Looper.loop()
|
||||
// Looper.loop() returned cleanly — main thread is shutting down.
|
||||
return@post
|
||||
} catch (e: Throwable) {
|
||||
if (!isSwallowable(e)) throw e
|
||||
Log.w("MainThreadCrashGuard", "Swallowed known framework crash: ${e.javaClass.name}: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSwallowable(e: Throwable): Boolean = isComposeLegacyCursorAnchorInfoCrash(e)
|
||||
|
||||
private fun isComposeLegacyCursorAnchorInfoCrash(e: Throwable): Boolean {
|
||||
if (e !is IllegalArgumentException) return false
|
||||
if (e.message != "endOffset must be greater than startOffset") return false
|
||||
// Cap the scan: in production this stack is ~25 frames; 40 is generous and bounds the
|
||||
// cost of every main-thread exception we don't swallow.
|
||||
val frames = e.stackTrace
|
||||
val limit = minOf(frames.size, 40)
|
||||
for (i in 0 until limit) {
|
||||
val frame = frames[i]
|
||||
val cls = frame.className
|
||||
if (cls.contains("LegacyCursorAnchorInfo") ||
|
||||
cls.contains("MultiParagraph") ||
|
||||
cls.contains("AndroidParagraph") ||
|
||||
cls.endsWith("TextLayout")
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -36,16 +36,16 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -63,7 +63,6 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
@@ -81,6 +80,7 @@ import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery
|
||||
import com.vitorpamplona.amethyst.ui.components.BechLink
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadUrlPreview
|
||||
import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.PostingTopBar
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
@@ -300,8 +300,9 @@ fun EditPostView(
|
||||
stringRes(id = R.string.lightning_invoice),
|
||||
stringRes(id = R.string.lightning_create_and_add_invoice),
|
||||
onNewInvoice = {
|
||||
postViewModel.message =
|
||||
TextFieldValue(postViewModel.message.text + "\n\n" + it)
|
||||
postViewModel.message.setTextAndPlaceCursorAtEnd(
|
||||
postViewModel.message.text.toString() + "\n\n" + it,
|
||||
)
|
||||
postViewModel.wantsInvoice = false
|
||||
},
|
||||
onError = { title, message -> accountViewModel.toastManager.toast(title, message) },
|
||||
@@ -309,43 +310,6 @@ fun EditPostView(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringRes(R.string.message_to_author),
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
)
|
||||
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
|
||||
MyTextField(
|
||||
value = postViewModel.subject,
|
||||
onValueChange = { postViewModel.updateSubject(it) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.message_to_author_placeholder),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
visualTransformation =
|
||||
UrlUserTagTransformation(
|
||||
MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
colors =
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
focusedBorderColor = Color.Transparent,
|
||||
),
|
||||
)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,9 +380,11 @@ private fun MessageField(postViewModel: EditPostViewModel) {
|
||||
}
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
value = postViewModel.message,
|
||||
onValueChange = { postViewModel.updateMessage(it) },
|
||||
ThinPaddingTextField(
|
||||
state = postViewModel.message,
|
||||
onTextChanged = { postViewModel.onMessageChanged() },
|
||||
inputTransformation = MentionPreservingInputTransformation,
|
||||
outputTransformation = UrlUserTagOutputTransformation(MaterialTheme.colorScheme.primary),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
@@ -443,11 +409,10 @@ private fun MessageField(postViewModel: EditPostViewModel) {
|
||||
)
|
||||
},
|
||||
colors =
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = Color.Transparent,
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
TextFieldDefaults.colors(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
visualTransformation = UrlUserTagTransformation(MaterialTheme.colorScheme.primary),
|
||||
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
package com.vitorpamplona.amethyst.ui.actions
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -77,7 +79,7 @@ open class EditPostViewModel : ViewModel() {
|
||||
var nip95attachments by
|
||||
mutableStateOf<List<Pair<FileStorageEvent, FileStorageHeaderEvent>>>(emptyList())
|
||||
|
||||
var message by mutableStateOf(TextFieldValue(""))
|
||||
val message = TextFieldState()
|
||||
var urlPreview by mutableStateOf<String?>(null)
|
||||
val mediaUploadTracker = MediaUploadTracker()
|
||||
val isUploadingImage: Boolean get() = mediaUploadTracker.isUploadingImage
|
||||
@@ -111,7 +113,7 @@ open class EditPostViewModel : ViewModel() {
|
||||
canAddInvoice = accountViewModel.userProfile().lnAddress() != null
|
||||
multiOrchestrator = null
|
||||
|
||||
message = TextFieldValue(versionLookingAt?.event?.content ?: edit.event?.content ?: "")
|
||||
message.setTextAndPlaceCursorAtEnd(versionLookingAt?.event?.content ?: edit.event?.content ?: "")
|
||||
urlPreview = findUrlInMessage()
|
||||
|
||||
this.editedFromNote = edit
|
||||
@@ -141,7 +143,7 @@ open class EditPostViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
account.sendEdit(
|
||||
message = message.text,
|
||||
message = message.text.toString(),
|
||||
originalNote = editedFromNote!!,
|
||||
notify = notify,
|
||||
summary = subject.text.ifBlank { null },
|
||||
@@ -248,7 +250,7 @@ open class EditPostViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
message = message.insertUrlAtCursor(urls.joinToString(" "))
|
||||
message.insertUrlAtCursor(urls.joinToString(" "))
|
||||
urlPreview = findUrlInMessage()
|
||||
|
||||
this@EditPostViewModel.multiOrchestrator = null
|
||||
@@ -264,7 +266,7 @@ open class EditPostViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
open fun cancel() {
|
||||
message = TextFieldValue("")
|
||||
message.setTextAndPlaceCursorAtEnd("")
|
||||
subject = TextFieldValue("")
|
||||
|
||||
editedFromNote = null
|
||||
@@ -280,17 +282,16 @@ open class EditPostViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
open fun findUrlInMessage(): String? =
|
||||
message.text.split('\n').firstNotNullOfOrNull { paragraph ->
|
||||
message.text.toString().split('\n').firstNotNullOfOrNull { paragraph ->
|
||||
paragraph.split(' ').firstOrNull { word: String ->
|
||||
RichTextParser.isValidURL(word) || RichTextParser.isUrlWithoutScheme(word)
|
||||
}
|
||||
}
|
||||
|
||||
open fun updateMessage(it: TextFieldValue) {
|
||||
message = it
|
||||
open fun onMessageChanged() {
|
||||
urlPreview = findUrlInMessage()
|
||||
|
||||
if (it.selection.collapsed) {
|
||||
if (message.selection.collapsed) {
|
||||
val lastWord = message.currentWord()
|
||||
if (lastWord.startsWith("@")) {
|
||||
userSuggestionsMainMessage = UserSuggestionAnchor.MAIN_MESSAGE
|
||||
@@ -305,7 +306,7 @@ open class EditPostViewModel : ViewModel() {
|
||||
open fun autocompleteWithUser(item: User) {
|
||||
userSuggestions?.let { userSuggestions ->
|
||||
val lastWord = message.currentWord()
|
||||
message = userSuggestions.replaceCurrentWord(message, lastWord, item)
|
||||
userSuggestions.replaceCurrentWord(message, lastWord, item)
|
||||
|
||||
userSuggestionsMainMessage = null
|
||||
userSuggestions.reset()
|
||||
|
||||
-229
@@ -1,229 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 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.ui.actions
|
||||
|
||||
import android.util.Patterns
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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.TransformedText
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
data class RangesChanges(
|
||||
val original: TextRange,
|
||||
val modified: TextRange,
|
||||
)
|
||||
|
||||
class UrlUserTagTransformation(
|
||||
val color: Color,
|
||||
) : VisualTransformation {
|
||||
override fun filter(text: AnnotatedString): TransformedText = buildAnnotatedStringWithUrlHighlighting(text, color)
|
||||
}
|
||||
|
||||
fun buildAnnotatedStringWithUrlHighlighting(
|
||||
text: AnnotatedString,
|
||||
color: Color,
|
||||
): TransformedText {
|
||||
val substitutions = mutableListOf<RangesChanges>()
|
||||
|
||||
val newText =
|
||||
buildAnnotatedString {
|
||||
val builderBefore = StringBuilder() // important to correctly measure Tag start and end
|
||||
val builderAfter = StringBuilder() // important to correctly measure Tag start and end
|
||||
append(
|
||||
text.text.split('\n').joinToString("\n") { paragraph: String ->
|
||||
paragraph.split(' ').joinToString(" ") { word: String ->
|
||||
try {
|
||||
val mention = parseUserMention(word)
|
||||
if (mention != null) {
|
||||
val (keyPortion, restOfWord, hexKey) = mention
|
||||
|
||||
val startIndex = builderBefore.toString().length
|
||||
|
||||
builderBefore.append(
|
||||
"$keyPortion$restOfWord ",
|
||||
) // accounts for the \n at the end of each paragraph
|
||||
|
||||
val endIndex = startIndex + keyPortion.length
|
||||
|
||||
val user = LocalCache.getOrCreateUser(hexKey)
|
||||
|
||||
val newWord = "@${user.toBestDisplayName()}"
|
||||
val startNew = builderAfter.toString().length
|
||||
|
||||
builderAfter.append(
|
||||
"$newWord$restOfWord ",
|
||||
) // accounts for the \n at the end of each paragraph
|
||||
|
||||
substitutions.add(
|
||||
RangesChanges(
|
||||
TextRange(startIndex, endIndex),
|
||||
TextRange(startNew, startNew + newWord.length),
|
||||
),
|
||||
)
|
||||
newWord + restOfWord
|
||||
} else if (Patterns.WEB_URL.matcher(word).matches()) {
|
||||
val startIndex = builderBefore.toString().length
|
||||
val endIndex = startIndex + word.length
|
||||
|
||||
val startNew = builderAfter.toString().length
|
||||
val endNew = startNew + word.length
|
||||
|
||||
substitutions.add(
|
||||
RangesChanges(
|
||||
TextRange(startIndex, endIndex),
|
||||
TextRange(startNew, endNew),
|
||||
),
|
||||
)
|
||||
|
||||
builderBefore.append("$word ")
|
||||
builderAfter.append("$word ")
|
||||
word
|
||||
} else {
|
||||
builderBefore.append("$word ")
|
||||
builderAfter.append("$word ")
|
||||
word
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
// if it can't parse the key, don't try to change.
|
||||
builderBefore.append("$word ")
|
||||
builderAfter.append("$word ")
|
||||
word
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
substitutions.forEach {
|
||||
addStyle(
|
||||
style =
|
||||
SpanStyle(
|
||||
color = color,
|
||||
textDecoration = TextDecoration.None,
|
||||
),
|
||||
start = it.modified.start,
|
||||
end = it.modified.end,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val numberOffsetTranslator =
|
||||
object : OffsetMapping {
|
||||
// Treat each substitution as an atomic wedge: any cursor position that falls
|
||||
// strictly inside a substituted range snaps to the wedge's trailing edge.
|
||||
// Without this, an IME (e.g. SwiftKey in extracted-text mode) can place the
|
||||
// cursor in the middle of an "@npub1..." mention, and a subsequent backspace
|
||||
// deletes a char from inside the bech32, breaking the npub and "expanding"
|
||||
// the collapsed mention.
|
||||
override fun originalToTransformed(offset: Int): Int {
|
||||
val inInsideRange =
|
||||
substitutions.firstOrNull { offset > it.original.start && offset < it.original.end }
|
||||
|
||||
if (inInsideRange != null) {
|
||||
return inInsideRange.modified.end
|
||||
}
|
||||
|
||||
val lastRangeThrough = substitutions.lastOrNull { offset >= it.original.end }
|
||||
|
||||
return if (lastRangeThrough != null) {
|
||||
lastRangeThrough.modified.end + (offset - lastRangeThrough.original.end)
|
||||
} else {
|
||||
offset
|
||||
}
|
||||
}
|
||||
|
||||
override fun transformedToOriginal(offset: Int): Int {
|
||||
val inInsideRange =
|
||||
substitutions.firstOrNull { offset > it.modified.start && offset < it.modified.end }
|
||||
|
||||
if (inInsideRange != null) {
|
||||
return inInsideRange.original.end
|
||||
}
|
||||
|
||||
val lastRangeThrough = substitutions.lastOrNull { offset >= it.modified.end }
|
||||
|
||||
return if (lastRangeThrough != null) {
|
||||
lastRangeThrough.original.end + (offset - lastRangeThrough.modified.end)
|
||||
} else {
|
||||
offset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TransformedText(
|
||||
newText,
|
||||
numberOffsetTranslator,
|
||||
)
|
||||
}
|
||||
|
||||
private data class UserMention(
|
||||
val keyPortion: String,
|
||||
val restOfWord: String,
|
||||
val hexKey: String,
|
||||
)
|
||||
|
||||
private fun parseUserMention(word: String): UserMention? {
|
||||
var key = word
|
||||
val prefix: String
|
||||
|
||||
if (key.startsWith("nostr:", true)) {
|
||||
prefix = key.substring(0, 6)
|
||||
key = key.substring(6)
|
||||
} else if (key.startsWith("@")) {
|
||||
prefix = "@"
|
||||
key = key.substring(1)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
if (key.startsWith("npub1", true) && key.length >= 63) {
|
||||
val keyB32 = key.substring(0, 63)
|
||||
val restOfWord = key.substring(63)
|
||||
val hex = decodePublicKeyAsHexOrNull(keyB32) ?: return null
|
||||
return UserMention("$prefix$keyB32", restOfWord, hex)
|
||||
} else if (key.startsWith("nprofile1", true)) {
|
||||
val parsed = Nip19Parser.uriToRoute(key) ?: return null
|
||||
val entity = parsed.entity
|
||||
if (entity !is NProfile && entity !is NPub) return null
|
||||
val hex =
|
||||
when (entity) {
|
||||
is NProfile -> entity.hex
|
||||
is NPub -> entity.hex
|
||||
else -> return null
|
||||
}
|
||||
val bech32Len = parsed.nip19raw.length
|
||||
val restOfWord = key.substring(bech32Len)
|
||||
return UserMention("$prefix${parsed.nip19raw}", restOfWord, hex)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
+14
-8
@@ -29,19 +29,22 @@ import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
|
||||
import com.vitorpamplona.amethyst.ui.actions.MentionPreservingInputTransformation
|
||||
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation
|
||||
import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField
|
||||
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@@ -123,9 +126,11 @@ fun ForwardZapTo(
|
||||
}
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
value = postViewModel.forwardZapToEditting.value,
|
||||
onValueChange = { postViewModel.updateZapForwardTo(it) },
|
||||
ThinPaddingTextField(
|
||||
state = postViewModel.forwardZapToEditting,
|
||||
onTextChanged = postViewModel::onForwardZapTextChanged,
|
||||
inputTransformation = MentionPreservingInputTransformation,
|
||||
outputTransformation = UrlUserTagOutputTransformation(MaterialTheme.colorScheme.primary),
|
||||
label = { Text(text = stringRes(R.string.zap_split_search_and_add_user)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = {
|
||||
@@ -135,9 +140,10 @@ fun ForwardZapTo(
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
visualTransformation =
|
||||
UrlUserTagTransformation(
|
||||
MaterialTheme.colorScheme.primary,
|
||||
colors =
|
||||
TextFieldDefaults.colors(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
),
|
||||
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
|
||||
)
|
||||
|
||||
+3
-3
@@ -20,15 +20,15 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.creators.zapsplits
|
||||
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
|
||||
@Stable
|
||||
interface IZapField {
|
||||
val forwardZapTo: MutableState<SplitBuilder<User>>
|
||||
val forwardZapToEditting: MutableState<TextFieldValue>
|
||||
val forwardZapToEditting: TextFieldState
|
||||
|
||||
fun updateZapFromText()
|
||||
|
||||
@@ -37,5 +37,5 @@ interface IZapField {
|
||||
sliderValue: Float,
|
||||
)
|
||||
|
||||
fun updateZapForwardTo(newZapForwardTo: TextFieldValue)
|
||||
fun onForwardZapTextChanged()
|
||||
}
|
||||
|
||||
+8
-9
@@ -22,13 +22,13 @@ package com.vitorpamplona.amethyst.ui.note.nip22Comments
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
@@ -194,7 +194,7 @@ open class CommentPostViewModel :
|
||||
// Forward Zap to
|
||||
var wantsForwardZapTo by mutableStateOf(false)
|
||||
override var forwardZapTo = mutableStateOf<SplitBuilder<User>>(SplitBuilder())
|
||||
override var forwardZapToEditting = mutableStateOf(TextFieldValue(""))
|
||||
override val forwardZapToEditting = TextFieldState()
|
||||
|
||||
// NSFW, Sensitive
|
||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||
@@ -389,7 +389,7 @@ open class CommentPostViewModel :
|
||||
val value = it.last().toFloatOrNull() ?: 0f
|
||||
forwardZapTo.value.addItem(user, value)
|
||||
}
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
wantsForwardZapTo = localForwardZapTo.isNotEmpty()
|
||||
|
||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||
@@ -713,7 +713,7 @@ open class CommentPostViewModel :
|
||||
wantsAnonymousPost = false
|
||||
|
||||
forwardZapTo.value = SplitBuilder()
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
|
||||
urlPreviews.reset()
|
||||
|
||||
@@ -753,10 +753,9 @@ open class CommentPostViewModel :
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
override fun updateZapForwardTo(newZapForwardTo: TextFieldValue) {
|
||||
forwardZapToEditting.value = newZapForwardTo
|
||||
if (newZapForwardTo.selection.collapsed) {
|
||||
val lastWord = newZapForwardTo.text
|
||||
override fun onForwardZapTextChanged() {
|
||||
if (forwardZapToEditting.selection.collapsed) {
|
||||
val lastWord = forwardZapToEditting.text.toString()
|
||||
userSuggestionsMainMessage = UserSuggestionAnchor.FORWARD_ZAPS
|
||||
userSuggestions?.processCurrentWord(lastWord)
|
||||
}
|
||||
@@ -770,7 +769,7 @@ open class CommentPostViewModel :
|
||||
urlPreviews.update(message.text.toString())
|
||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
||||
forwardZapTo.value.addItem(item)
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
}
|
||||
|
||||
userSuggestionsMainMessage = null
|
||||
|
||||
+8
-9
@@ -22,13 +22,13 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
@@ -229,7 +229,7 @@ class ChatNewMessageViewModel :
|
||||
// Forward Zap to
|
||||
var wantsForwardZapTo by mutableStateOf(false)
|
||||
override val forwardZapTo = mutableStateOf<SplitBuilder<User>>(SplitBuilder())
|
||||
override val forwardZapToEditting = mutableStateOf(TextFieldValue(""))
|
||||
override val forwardZapToEditting = TextFieldState()
|
||||
|
||||
// NSFW, Sensitive
|
||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||
@@ -347,7 +347,7 @@ class ChatNewMessageViewModel :
|
||||
}
|
||||
// don't support editing old-style splits.
|
||||
}
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
wantsForwardZapTo = localForwardZapTo.isNotEmpty()
|
||||
|
||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||
@@ -651,7 +651,7 @@ class ChatNewMessageViewModel :
|
||||
wantsSecretEmoji = false
|
||||
|
||||
forwardZapTo.value = SplitBuilder()
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
|
||||
urlPreviews.reset()
|
||||
|
||||
@@ -724,10 +724,9 @@ class ChatNewMessageViewModel :
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
override fun updateZapForwardTo(newZapForwardTo: TextFieldValue) {
|
||||
forwardZapToEditting.value = newZapForwardTo
|
||||
if (newZapForwardTo.selection.collapsed) {
|
||||
val lastWord = newZapForwardTo.text
|
||||
override fun onForwardZapTextChanged() {
|
||||
if (forwardZapToEditting.selection.collapsed) {
|
||||
val lastWord = forwardZapToEditting.text.toString()
|
||||
userSuggestionsMainMessage = UserSuggestionAnchor.FORWARD_ZAPS
|
||||
userSuggestions?.processCurrentWord(lastWord)
|
||||
}
|
||||
@@ -744,7 +743,7 @@ class ChatNewMessageViewModel :
|
||||
|
||||
UserSuggestionAnchor.FORWARD_ZAPS -> {
|
||||
forwardZapTo.value.addItem(item)
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
}
|
||||
|
||||
UserSuggestionAnchor.TO_USERS -> {
|
||||
|
||||
+8
-9
@@ -22,13 +22,13 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.send
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
@@ -156,7 +156,7 @@ open class ChannelNewMessageViewModel :
|
||||
// Forward Zap to
|
||||
var wantsForwardZapTo by mutableStateOf(false)
|
||||
var forwardZapTo by mutableStateOf<SplitBuilder<User>>(SplitBuilder())
|
||||
var forwardZapToEditting by mutableStateOf(TextFieldValue(""))
|
||||
val forwardZapToEditting = TextFieldState()
|
||||
|
||||
// NSFW, Sensitive
|
||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||
@@ -240,7 +240,7 @@ open class ChannelNewMessageViewModel :
|
||||
}
|
||||
// don't support edditing old-style splits.
|
||||
}
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
wantsForwardZapTo = localForwardZapTo.isNotEmpty()
|
||||
|
||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||
@@ -561,7 +561,7 @@ open class ChannelNewMessageViewModel :
|
||||
wantsToAddGeoHash = false
|
||||
|
||||
forwardZapTo = SplitBuilder()
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
|
||||
userSuggestions?.reset()
|
||||
userSuggestionsMainMessage = null
|
||||
@@ -599,10 +599,9 @@ open class ChannelNewMessageViewModel :
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
open fun updateZapForwardTo(newZapForwardTo: TextFieldValue) {
|
||||
forwardZapToEditting = newZapForwardTo
|
||||
if (newZapForwardTo.selection.collapsed) {
|
||||
val lastWord = newZapForwardTo.text
|
||||
open fun onForwardZapTextChanged() {
|
||||
if (forwardZapToEditting.selection.collapsed) {
|
||||
val lastWord = forwardZapToEditting.text.toString()
|
||||
userSuggestionsMainMessage = UserSuggestionAnchor.FORWARD_ZAPS
|
||||
userSuggestions?.processCurrentWord(lastWord)
|
||||
}
|
||||
@@ -615,7 +614,7 @@ open class ChannelNewMessageViewModel :
|
||||
it.replaceCurrentWord(message, lastWord, item)
|
||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
||||
forwardZapTo.addItem(item)
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
}
|
||||
|
||||
userSuggestionsMainMessage = null
|
||||
|
||||
+8
-8
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -181,7 +182,7 @@ class LongFormPostViewModel :
|
||||
// Forward Zap to
|
||||
var wantsForwardZapTo by mutableStateOf(false)
|
||||
override var forwardZapTo = mutableStateOf<SplitBuilder<User>>(SplitBuilder())
|
||||
override var forwardZapToEditting = mutableStateOf(TextFieldValue(""))
|
||||
override val forwardZapToEditting = TextFieldState()
|
||||
|
||||
// NSFW, Sensitive
|
||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||
@@ -290,7 +291,7 @@ class LongFormPostViewModel :
|
||||
val value = it.last().toFloatOrNull() ?: 0f
|
||||
forwardZapTo.value.addItem(user, value)
|
||||
}
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
wantsForwardZapTo = localForwardZapTo.isNotEmpty()
|
||||
|
||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||
@@ -603,7 +604,7 @@ class LongFormPostViewModel :
|
||||
wantsSecretEmoji = false
|
||||
|
||||
forwardZapTo.value = SplitBuilder()
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
|
||||
userSuggestions?.reset()
|
||||
userSuggestionsMainMessage = null
|
||||
@@ -634,10 +635,9 @@ class LongFormPostViewModel :
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
override fun updateZapForwardTo(newZapForwardTo: TextFieldValue) {
|
||||
forwardZapToEditting.value = newZapForwardTo
|
||||
if (newZapForwardTo.selection.collapsed) {
|
||||
val lastWord = newZapForwardTo.text
|
||||
override fun onForwardZapTextChanged() {
|
||||
if (forwardZapToEditting.selection.collapsed) {
|
||||
val lastWord = forwardZapToEditting.text.toString()
|
||||
userSuggestionsMainMessage = UserSuggestionAnchor.FORWARD_ZAPS
|
||||
userSuggestions?.processCurrentWord(lastWord)
|
||||
}
|
||||
@@ -650,7 +650,7 @@ class LongFormPostViewModel :
|
||||
userSuggestions.replaceCurrentWord(message, lastWord, item)
|
||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
||||
forwardZapTo.value.addItem(item)
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
}
|
||||
|
||||
userSuggestionsMainMessage = null
|
||||
|
||||
+8
-9
@@ -22,13 +22,13 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
@@ -168,7 +168,7 @@ open class NewProductViewModel :
|
||||
// Forward Zap to
|
||||
var wantsForwardZapTo by mutableStateOf(false)
|
||||
override var forwardZapTo = mutableStateOf<SplitBuilder<User>>(SplitBuilder())
|
||||
override var forwardZapToEditting = mutableStateOf(TextFieldValue(""))
|
||||
override val forwardZapToEditting = TextFieldState()
|
||||
|
||||
// NSFW, Sensitive
|
||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||
@@ -266,7 +266,7 @@ open class NewProductViewModel :
|
||||
val value = it.last().toFloatOrNull() ?: 0f
|
||||
forwardZapTo.value.addItem(user, value)
|
||||
}
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
wantsForwardZapTo = localfowardZapTo.isNotEmpty()
|
||||
|
||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||
@@ -491,7 +491,7 @@ open class NewProductViewModel :
|
||||
wantsSecretEmoji = false
|
||||
|
||||
forwardZapTo.value = SplitBuilder()
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
|
||||
urlPreviews.reset()
|
||||
|
||||
@@ -535,10 +535,9 @@ open class NewProductViewModel :
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
override fun updateZapForwardTo(newZapForwardTo: TextFieldValue) {
|
||||
forwardZapToEditting.value = newZapForwardTo
|
||||
if (newZapForwardTo.selection.collapsed) {
|
||||
val lastWord = newZapForwardTo.text
|
||||
override fun onForwardZapTextChanged() {
|
||||
if (forwardZapToEditting.selection.collapsed) {
|
||||
val lastWord = forwardZapToEditting.text.toString()
|
||||
userSuggestionsMainMessage = UserSuggestionAnchor.FORWARD_ZAPS
|
||||
userSuggestions?.processCurrentWord(lastWord)
|
||||
}
|
||||
@@ -552,7 +551,7 @@ open class NewProductViewModel :
|
||||
urlPreviews.update(message.text.toString())
|
||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
||||
forwardZapTo.value.addItem(item)
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
}
|
||||
|
||||
userSuggestionsMainMessage = null
|
||||
|
||||
+10
-11
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -30,7 +31,6 @@ import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
@@ -283,7 +283,7 @@ open class ShortNotePostViewModel :
|
||||
// Forward Zap to
|
||||
var wantsForwardZapTo by mutableStateOf(false)
|
||||
override var forwardZapTo = mutableStateOf<SplitBuilder<User>>(SplitBuilder())
|
||||
override var forwardZapToEditting = mutableStateOf(TextFieldValue(""))
|
||||
override val forwardZapToEditting = TextFieldState()
|
||||
|
||||
// NSFW, Sensitive
|
||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||
@@ -578,7 +578,7 @@ open class ShortNotePostViewModel :
|
||||
val value = it.last().toFloatOrNull() ?: 0f
|
||||
forwardZapTo.value.addItem(user, value)
|
||||
}
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
wantsForwardZapTo = localForwardZapTo.isNotEmpty()
|
||||
|
||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||
@@ -664,7 +664,7 @@ open class ShortNotePostViewModel :
|
||||
val value = it.last().toFloatOrNull() ?: 0f
|
||||
forwardZapTo.value.addItem(user, value)
|
||||
}
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
wantsForwardZapTo = localForwardZapTo.isNotEmpty()
|
||||
|
||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||
@@ -736,7 +736,7 @@ open class ShortNotePostViewModel :
|
||||
val value = it.last().toFloatOrNull() ?: 0f
|
||||
forwardZapTo.value.addItem(user, value)
|
||||
}
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
wantsForwardZapTo = localForwardZapTo.isNotEmpty()
|
||||
|
||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||
@@ -1238,7 +1238,7 @@ open class ShortNotePostViewModel :
|
||||
scheduledForSec = null
|
||||
|
||||
forwardZapTo.value = SplitBuilder()
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
|
||||
urlPreviews.reset()
|
||||
|
||||
@@ -1283,10 +1283,9 @@ open class ShortNotePostViewModel :
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
override fun updateZapForwardTo(newZapForwardTo: TextFieldValue) {
|
||||
forwardZapToEditting.value = newZapForwardTo
|
||||
if (newZapForwardTo.selection.collapsed) {
|
||||
val lastWord = newZapForwardTo.text
|
||||
override fun onForwardZapTextChanged() {
|
||||
if (forwardZapToEditting.selection.collapsed) {
|
||||
val lastWord = forwardZapToEditting.text.toString()
|
||||
userSuggestionsMainMessage = UserSuggestionAnchor.FORWARD_ZAPS
|
||||
userSuggestions?.processCurrentWord(lastWord)
|
||||
}
|
||||
@@ -1300,7 +1299,7 @@ open class ShortNotePostViewModel :
|
||||
urlPreviews.update(message.text.toString())
|
||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
||||
forwardZapTo.value.addItem(item)
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
}
|
||||
|
||||
userSuggestionsMainMessage = null
|
||||
|
||||
+8
-9
@@ -22,13 +22,13 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.chat
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
@@ -162,7 +162,7 @@ open class NestNewMessageViewModel :
|
||||
// Forward Zap to
|
||||
var wantsForwardZapTo by mutableStateOf(false)
|
||||
var forwardZapTo by mutableStateOf<SplitBuilder<User>>(SplitBuilder())
|
||||
var forwardZapToEditting by mutableStateOf(TextFieldValue(""))
|
||||
val forwardZapToEditting = TextFieldState()
|
||||
|
||||
// NSFW, Sensitive
|
||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||
@@ -246,7 +246,7 @@ open class NestNewMessageViewModel :
|
||||
}
|
||||
// don't support edditing old-style splits.
|
||||
}
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
wantsForwardZapTo = localForwardZapTo.isNotEmpty()
|
||||
|
||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||
@@ -480,7 +480,7 @@ open class NestNewMessageViewModel :
|
||||
wantsToAddGeoHash = false
|
||||
|
||||
forwardZapTo = SplitBuilder()
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
|
||||
userSuggestions?.reset()
|
||||
userSuggestionsMainMessage = null
|
||||
@@ -518,10 +518,9 @@ open class NestNewMessageViewModel :
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
open fun updateZapForwardTo(newZapForwardTo: TextFieldValue) {
|
||||
forwardZapToEditting = newZapForwardTo
|
||||
if (newZapForwardTo.selection.collapsed) {
|
||||
val lastWord = newZapForwardTo.text
|
||||
open fun onForwardZapTextChanged() {
|
||||
if (forwardZapToEditting.selection.collapsed) {
|
||||
val lastWord = forwardZapToEditting.text.toString()
|
||||
userSuggestionsMainMessage = UserSuggestionAnchor.FORWARD_ZAPS
|
||||
userSuggestions?.processCurrentWord(lastWord)
|
||||
}
|
||||
@@ -534,7 +533,7 @@ open class NestNewMessageViewModel :
|
||||
it.replaceCurrentWord(message, lastWord, item)
|
||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
||||
forwardZapTo.addItem(item)
|
||||
forwardZapToEditting = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
}
|
||||
|
||||
userSuggestionsMainMessage = null
|
||||
|
||||
+8
-9
@@ -22,13 +22,13 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.publicMessag
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
import androidx.compose.foundation.text.input.clearText
|
||||
import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
@@ -180,7 +180,7 @@ class NewPublicMessageViewModel :
|
||||
// Forward Zap to
|
||||
var wantsForwardZapTo by mutableStateOf(false)
|
||||
override val forwardZapTo = mutableStateOf<SplitBuilder<User>>(SplitBuilder())
|
||||
override val forwardZapToEditting = mutableStateOf(TextFieldValue(""))
|
||||
override val forwardZapToEditting = TextFieldState()
|
||||
|
||||
// NSFW, Sensitive
|
||||
var wantsToMarkAsSensitive by mutableStateOf(false)
|
||||
@@ -282,7 +282,7 @@ class NewPublicMessageViewModel :
|
||||
}
|
||||
// don't support editing old-style splits.
|
||||
}
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
wantsForwardZapTo = localForwardZapTo.isNotEmpty()
|
||||
|
||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||
@@ -535,7 +535,7 @@ class NewPublicMessageViewModel :
|
||||
wantsSecretEmoji = false
|
||||
|
||||
forwardZapTo.value = SplitBuilder()
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
|
||||
urlPreviews.reset()
|
||||
|
||||
@@ -587,10 +587,9 @@ class NewPublicMessageViewModel :
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
override fun updateZapForwardTo(newZapForwardTo: TextFieldValue) {
|
||||
forwardZapToEditting.value = newZapForwardTo
|
||||
if (newZapForwardTo.selection.collapsed) {
|
||||
val lastWord = newZapForwardTo.text
|
||||
override fun onForwardZapTextChanged() {
|
||||
if (forwardZapToEditting.selection.collapsed) {
|
||||
val lastWord = forwardZapToEditting.text.toString()
|
||||
userSuggestionsMainMessage = UserSuggestionAnchor.FORWARD_ZAPS
|
||||
userSuggestions?.processCurrentWord(lastWord)
|
||||
}
|
||||
@@ -607,7 +606,7 @@ class NewPublicMessageViewModel :
|
||||
|
||||
UserSuggestionAnchor.FORWARD_ZAPS -> {
|
||||
forwardZapTo.value.addItem(item)
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
forwardZapToEditting.clearText()
|
||||
}
|
||||
|
||||
UserSuggestionAnchor.TO_USERS -> {
|
||||
|
||||
Reference in New Issue
Block a user