diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/InformationDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/InformationDialog.kt index bb6e12df3..2b4a9f413 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/InformationDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/InformationDialog.kt @@ -38,17 +38,19 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.components.toasts.ThrowableToastMsg import com.vitorpamplona.amethyst.ui.components.toasts.ThrowableToastMsg2 +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Size16dp import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import kotlinx.coroutines.launch import java.io.PrintWriter import java.io.StringWriter @@ -120,8 +122,11 @@ fun InformationDialog( ) { moreInfo?.let { val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() TextButton(onClick = { - clipboardManager.setText(AnnotatedString(it)) + scope.launch { + clipboardManager.setText(it) + } }) { Text(stringRes(R.string.copy_stack_to_clipboard)) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt index 23ded3a4c..deb0ffcbe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt @@ -40,13 +40,13 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.produceState import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -58,6 +58,7 @@ import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons import com.vitorpamplona.amethyst.service.cashu.CachedCashuParser import com.vitorpamplona.amethyst.service.cashu.CashuToken import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.note.CopyIcon import com.vitorpamplona.amethyst.ui.note.OpenInNewIcon import com.vitorpamplona.amethyst.ui.note.ZapIcon @@ -72,6 +73,7 @@ import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @Composable @@ -141,7 +143,6 @@ fun CashuPreviewNew( toast: (String, String) -> Unit, ) { val context = LocalContext.current - val clipboardManager = LocalClipboard.current Card( modifier = CashuCardBorders, @@ -230,10 +231,15 @@ fun CashuPreviewNew( Spacer(modifier = StdHorzSpacer) + val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() + FilledTonalButton( onClick = { - // Copying the token to clipboard - clipboardManager.setText(AnnotatedString(token.token)) + scope.launch { + // Copying the token to clipboard + clipboardManager.setText(token.token) + } }, shape = SmallishBorder, contentPadding = PaddingValues(0.dp), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRelayUrl.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRelayUrl.kt index a4cd9a2cb..1d55e843a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRelayUrl.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRelayUrl.kt @@ -25,12 +25,14 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextOverflow +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route +import kotlinx.coroutines.launch @Composable fun ClickableRelayUrl( @@ -38,11 +40,16 @@ fun ClickableRelayUrl( nav: INav, ) { val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() val clickableModifier = remember(relayUrl) { Modifier .combinedClickable( - onLongClick = { clipboardManager.setText(AnnotatedString(relayUrl)) }, + onLongClick = { + scope.launch { + clipboardManager.setText(relayUrl) + } + }, onClick = { nav.nav(Route.RelayInfo(relayUrl)) }, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt index 1996ed499..78027c4d0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt @@ -31,20 +31,22 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextOverflow import coil3.compose.AsyncImage import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.preview.UrlInfoItem +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.MaxWidthWithHorzPadding import com.vitorpamplona.amethyst.ui.theme.innerPostModifier import com.vitorpamplona.amethyst.ui.theme.previewCardImageModifier +import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable @@ -60,6 +62,7 @@ fun UrlPreviewCard( if (popupExpanded.value) { val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() M3ActionDialog( title = stringRes(R.string.link_actions_dialog_title), onDismiss = { popupExpanded.value = false }, @@ -69,8 +72,10 @@ fun UrlPreviewCard( icon = Icons.Outlined.ContentCopy, text = stringRes(R.string.copy_url_to_clipboard), ) { - clipboardManager.setText(AnnotatedString(url)) - popupExpanded.value = false + scope.launch { + clipboardManager.setText(url) + popupExpanded.value = false + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index ba8585ebf..21268e128 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -54,6 +54,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -62,7 +63,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.TextOverflow @@ -90,6 +90,7 @@ import com.vitorpamplona.amethyst.service.playback.composable.VideoView import com.vitorpamplona.amethyst.service.uploads.blossom.bud10.openBlossomUriAsIntent import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.actions.InformationDialog +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.note.BlankNote import com.vitorpamplona.amethyst.ui.note.DownloadForOfflineIcon import com.vitorpamplona.amethyst.ui.painterRes @@ -772,19 +773,24 @@ fun ShareMediaAction( onDismiss = { if (!isDownloadingVideo.value) onDismiss() }, ) { val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() // Copy & Gallery section if ((videoUri != null && !videoUri.startsWith("file")) || postNostrUri != null) { M3ActionSection { if (videoUri != null && !videoUri.startsWith("file")) { M3ActionRow(icon = Icons.Outlined.Link, text = stringRes(R.string.copy_url_to_clipboard)) { - clipboardManager.setText(AnnotatedString(videoUri)) + scope.launch { + clipboardManager.setText(videoUri) + } onDismiss() } } postNostrUri?.let { M3ActionRow(icon = Icons.Outlined.ContentCopy, text = stringRes(R.string.copy_the_note_id_to_the_clipboard)) { - clipboardManager.setText(AnnotatedString(it)) + scope.launch { + clipboardManager.setText(it) + } onDismiss() } M3ActionRow(icon = Icons.Outlined.Collections, text = stringRes(R.string.add_media_to_gallery)) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/util/ClipboardExt.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/util/ClipboardExt.kt new file mode 100644 index 000000000..8b3b7a0c1 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/util/ClipboardExt.kt @@ -0,0 +1,36 @@ +/* + * 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.components.util + +import android.content.ClipData +import androidx.compose.ui.platform.ClipEntry +import androidx.compose.ui.platform.Clipboard + +suspend fun Clipboard.setText(text: String) { + setClipEntry(ClipEntry(ClipData.newPlainText("", text))) +} + +suspend fun Clipboard.getText(): String? = + getClipEntry() + ?.clipData + ?.getItemAt(0) + ?.text + ?.toString() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt index 797a99848..6b934d34d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt @@ -70,7 +70,6 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -80,6 +79,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.routeEditDraftTo import com.vitorpamplona.amethyst.ui.painterRes @@ -289,8 +289,10 @@ fun CardBody( label = stringRes(R.string.quick_action_copy_text), ) { accountViewModel.decrypt(note) { - clipboardManager.setText(AnnotatedString(it)) - showToast(R.string.copied_note_text_to_clipboard) + scope.launch { + clipboardManager.setText(it) + showToast(R.string.copied_note_text_to_clipboard) + } } onDismiss() @@ -302,7 +304,7 @@ fun CardBody( ) { note.author?.let { scope.launch { - clipboardManager.setText(AnnotatedString(it.toNostrUri())) + clipboardManager.setText(it.toNostrUri()) showToast(R.string.copied_user_id_to_clipboard) onDismiss() } @@ -314,7 +316,7 @@ fun CardBody( stringRes(R.string.quick_action_copy_note_id), ) { scope.launch { - clipboardManager.setText(AnnotatedString(note.toNostrUri())) + clipboardManager.setText(note.toNostrUri()) showToast(R.string.copied_note_id_to_clipboard) onDismiss() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt index 16e827b01..a08e1c933 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt @@ -39,11 +39,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -54,6 +54,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo import com.vitorpamplona.amethyst.ui.components.ClickableBox import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -71,6 +72,7 @@ import com.vitorpamplona.amethyst.ui.theme.relayIconModifier import com.vitorpamplona.amethyst.ui.theme.ripple24dp import com.vitorpamplona.amethyst.ui.theme.warningColorOnSecondSurface import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import kotlinx.coroutines.launch private const val DAMUS_RELAY_URL = "wss://relay.damus.io" @@ -130,6 +132,7 @@ fun RenderRelay( val relayInfo by loadRelayInfo(relay) val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() val clickableModifier = remember(relay) { Modifier @@ -138,7 +141,9 @@ fun RenderRelay( indication = ripple24dp, interactionSource = MutableInteractionSource(), onLongClick = { - clipboardManager.setText(AnnotatedString(relay.url)) + scope.launch { + clipboardManager.setText(relay.url) + } }, onClick = { nav.nav(Route.RelayInfo(relay.url)) }, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt index 97e98d9dd..ba4238425 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt @@ -84,6 +84,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -102,6 +103,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.components.TextSpinner import com.vitorpamplona.amethyst.ui.components.TitleExplainer +import com.vitorpamplona.amethyst.ui.components.util.getText import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.keyBackup.getFragmentActivity @@ -120,6 +122,7 @@ import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.launch @OptIn(ExperimentalLayoutApi::class) @Composable @@ -149,6 +152,7 @@ fun UpdateZapAmountContent( val context = LocalContext.current val clipboardManager = LocalClipboard.current val uri = LocalUriHandler.current + val scope = rememberCoroutineScope() val zapTypes = listOf( @@ -443,15 +447,17 @@ fun UpdateZapAmountContent( // Paste from clipboard IconButton( onClick = { - val clipText = clipboardManager.getText()?.text - try { - clipText?.let { postViewModel.copyFromClipboard(it) } - } catch (e: IllegalArgumentException) { - accountViewModel.toastManager.toast( - R.string.invalid_nip47_uri_title, - R.string.invalid_nip47_uri_description, - clipText ?: "", - ) + scope.launch { + val clipText = clipboardManager.getText() + try { + clipText?.let { postViewModel.copyFromClipboard(it) } + } catch (e: IllegalArgumentException) { + accountViewModel.toastManager.toast( + R.string.invalid_nip47_uri_title, + R.string.invalid_nip47_uri_description, + clipText ?: "", + ) + } } }, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt index 6d161c76f..566ead599 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt @@ -48,7 +48,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.AnnotatedString import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.AddressableNote @@ -59,6 +58,7 @@ import com.vitorpamplona.amethyst.ui.components.GenericLoadable import com.vitorpamplona.amethyst.ui.components.M3ActionDialog import com.vitorpamplona.amethyst.ui.components.M3ActionRow import com.vitorpamplona.amethyst.ui.components.M3ActionSection +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.routeEditDraftTo @@ -196,20 +196,24 @@ fun NoteDropDownMenu( M3ActionSection { M3ActionRow(icon = Icons.Outlined.ContentCopy, text = stringRes(R.string.copy_text)) { val lastNoteVersion = (editState?.value as? GenericLoadable.Loaded)?.loaded?.modificationToShow?.value ?: note - accountViewModel.decrypt(lastNoteVersion) { clipboardManager.setText(AnnotatedString(it)) } + accountViewModel.decrypt(lastNoteVersion) { + scope.launch { + clipboardManager.setText(it) + } + } onDismiss() } M3ActionRow(icon = Icons.Outlined.ContentCopy, text = stringRes(R.string.copy_user_pubkey)) { note.author?.let { scope.launch(Dispatchers.IO) { - clipboardManager.setText(AnnotatedString("nostr:${it.pubkeyNpub()}")) + clipboardManager.setText("nostr:${it.pubkeyNpub()}") onDismiss() } } } M3ActionRow(icon = Icons.Outlined.ContentCopy, text = stringRes(R.string.copy_note_id)) { scope.launch(Dispatchers.IO) { - clipboardManager.setText(AnnotatedString(note.toNostrUri())) + clipboardManager.setText(note.toNostrUri()) onDismiss() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt index 92cc13e6d..ad3c2f107 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/AppDefinition.kt @@ -42,6 +42,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -49,7 +50,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -63,6 +63,7 @@ import com.vitorpamplona.amethyst.ui.components.ClickableTextPrimary import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.note.LinkIcon import com.vitorpamplona.amethyst.ui.painterRes @@ -74,6 +75,7 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppMetadata import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @OptIn(ExperimentalFoundationApi::class) @@ -94,6 +96,7 @@ fun RenderAppDefinition( metadata?.let { theAppMetadata -> Box { val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() val uri = LocalUriHandler.current if (!theAppMetadata.banner.isNullOrBlank()) { @@ -109,7 +112,13 @@ fun RenderAppDefinition( .height(125.dp) .combinedClickable( onClick = {}, - onLongClick = { clipboardManager.setText(AnnotatedString(theAppMetadata.banner!!)) }, + onLongClick = { + theAppMetadata.banner?.let { + scope.launch { + clipboardManager.setText(it) + } + } + }, ), ) @@ -161,7 +170,11 @@ fun RenderAppDefinition( .background(MaterialTheme.colorScheme.background) .combinedClickable( onClick = { zoomImageDialogOpen = true }, - onLongClick = { clipboardManager.setText(AnnotatedString(picture)) }, + onLongClick = { + scope.launch { + clipboardManager.setText(picture) + } + }, ), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RelayList.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RelayList.kt index 7192aa9ad..ca15807f1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RelayList.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/RelayList.kt @@ -33,12 +33,12 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -49,6 +49,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.nip51Lists.relayLists.RelayListCard import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserRelayIntoList import com.vitorpamplona.amethyst.ui.components.ShowMoreButton +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.note.AddRelayButton @@ -63,6 +64,7 @@ import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent import com.vitorpamplona.quartz.nip51Lists.relaySets.RelaySetEvent import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.launch @Composable fun DisplayRelaySet( @@ -411,11 +413,14 @@ private fun RelayOptionsAction( ) { val isCurrentlyOnTheUsersList by observeUserRelayIntoList(relay, accountViewModel) val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() if (isCurrentlyOnTheUsersList) { AddRelayButton { - clipboardManager.setText(AnnotatedString(relay.url)) - nav.nav(Route.EditRelays) + scope.launch { + clipboardManager.setText(relay.url) + nav.nav(Route.EditRelays) + } } } else { RemoveRelayButton { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/display/BookmarkGroupItemOptions.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/display/BookmarkGroupItemOptions.kt index f45f6247a..d71f5cb30 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/display/BookmarkGroupItemOptions.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/display/BookmarkGroupItemOptions.kt @@ -43,7 +43,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.AnnotatedString import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note @@ -53,6 +52,7 @@ import com.vitorpamplona.amethyst.ui.components.GenericLoadable import com.vitorpamplona.amethyst.ui.components.M3ActionDialog import com.vitorpamplona.amethyst.ui.components.M3ActionRow import com.vitorpamplona.amethyst.ui.components.M3ActionSection +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.routeEditDraftTo @@ -202,20 +202,24 @@ fun BookmarkGroupItemOptionsMenu( M3ActionSection { M3ActionRow(icon = Icons.Outlined.ContentCopy, text = stringRes(R.string.copy_text)) { val lastNoteVersion = (editState?.value as? GenericLoadable.Loaded)?.loaded?.modificationToShow?.value ?: note - accountViewModel.decrypt(lastNoteVersion) { clipboardManager.setText(AnnotatedString(it)) } + accountViewModel.decrypt(lastNoteVersion) { + scope.launch { + clipboardManager.setText(it) + } + } onDismiss() } M3ActionRow(icon = Icons.Outlined.ContentCopy, text = stringRes(R.string.copy_user_pubkey)) { note.author?.let { scope.launch(Dispatchers.IO) { - clipboardManager.setText(AnnotatedString("nostr:${it.pubkeyNpub()}")) + clipboardManager.setText("nostr:${it.pubkeyNpub()}") onDismiss() } } } M3ActionRow(icon = Icons.Outlined.ContentCopy, text = stringRes(R.string.copy_note_id)) { scope.launch(Dispatchers.IO) { - clipboardManager.setText(AnnotatedString(note.toNostrUri())) + clipboardManager.setText(note.toNostrUri()) onDismiss() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderCreateChannelNote.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderCreateChannelNote.kt index 4c45e3eda..c798e382c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderCreateChannelNote.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderCreateChannelNote.kt @@ -37,12 +37,12 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -57,6 +57,7 @@ import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route @@ -74,6 +75,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent import com.vitorpamplona.quartz.nip28PublicChat.base.ChannelDataNorm +import kotlinx.coroutines.launch @Composable fun RenderCreateChannelNote( @@ -238,12 +240,15 @@ fun RenderRelayLinePublicChat( @Suppress("ProduceStateDoesNotAssignValue") val relayInfo by loadRelayInfo(relay) + val scope = rememberCoroutineScope() val clipboardManager = LocalClipboard.current val clickableModifier = remember(relay) { Modifier.combinedClickable( onLongClick = { - clipboardManager.setText(AnnotatedString(relay.url)) + scope.launch { + clipboardManager.setText(relay.url) + } }, onClick = { nav.nav(Route.RelayInfo(relay.url)) }, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupScreen.kt index 156cdf798..68bd1e6f8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupScreen.kt @@ -63,12 +63,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.autofill.ContentType -import androidx.compose.ui.platform.ClipboardManager +import androidx.compose.ui.platform.Clipboard import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.semantics.contentType import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation @@ -87,6 +86,7 @@ import com.halilibo.richtext.ui.material3.RichText import com.halilibo.richtext.ui.resolveDefaults import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.note.authenticate @@ -366,11 +366,11 @@ private fun copyNSec( context: Context, scope: CoroutineScope, account: Account, - clipboardManager: ClipboardManager, + clipboardManager: Clipboard, ) { account.settings.keyPair.privKey?.let { - clipboardManager.setText(AnnotatedString(it.toNsec())) scope.launch { + clipboardManager.setText(it.toNsec()) Toast .makeText( context, @@ -386,7 +386,7 @@ private fun encryptCopyNSec( context: Context, scope: CoroutineScope, accountViewModel: AccountViewModel, - clipboardManager: ClipboardManager, + clipboardManager: Clipboard, ) { if (password.value.text.isBlank()) { scope.launch { @@ -401,8 +401,8 @@ private fun encryptCopyNSec( accountViewModel.account.settings.keyPair.privKey?.let { val key = runCatching { Nip49().encrypt(it.toHexKey(), password.value.text) }.getOrNull() if (key != null) { - clipboardManager.setText(AnnotatedString(key)) scope.launch { + clipboardManager.setText(key) Toast .makeText( context, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawBanner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawBanner.kt index 5234f8fa0..1f2a822ef 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawBanner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawBanner.kt @@ -29,11 +29,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage import com.vitorpamplona.amethyst.R @@ -41,9 +41,11 @@ import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserBanner import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes +import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable @@ -64,6 +66,7 @@ fun DrawBanner( ) { if (!banner.isNullOrBlank()) { val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() var zoomImageDialogOpen by remember { mutableStateOf(false) } AsyncImage( @@ -78,7 +81,11 @@ fun DrawBanner( .height(150.dp) .combinedClickable( onClick = { zoomImageDialogOpen = true }, - onLongClick = { clipboardManager.setText(AnnotatedString(banner)) }, + onLongClick = { + scope.launch { + clipboardManager.setText(banner) + } + }, ), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt index 6d711e263..e730f3e0e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt @@ -28,18 +28,20 @@ import androidx.compose.material.icons.outlined.ContentCopy import androidx.compose.material.icons.outlined.Report import androidx.compose.material.icons.outlined.Share import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.text.AnnotatedString import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.components.M3ActionDialog import com.vitorpamplona.amethyst.ui.components.M3ActionRow import com.vitorpamplona.amethyst.ui.components.M3ActionSection +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.note.externalLinkForUser import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.nip56Reports.ReportType +import kotlinx.coroutines.launch @Composable fun UserProfileDropDownMenu( @@ -55,6 +57,7 @@ fun UserProfileDropDownMenu( onDismiss = onDismiss, ) { val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() val context = LocalContext.current // Share section @@ -63,8 +66,10 @@ fun UserProfileDropDownMenu( icon = Icons.Outlined.ContentCopy, text = stringRes(R.string.copy_user_id), ) { - clipboardManager.setText(AnnotatedString(user.pubkeyNpub())) - onDismiss() + scope.launch { + clipboardManager.setText(user.pubkeyNpub()) + onDismiss() + } } M3ActionRow( icon = Icons.Outlined.Share, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/RelayFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/RelayFeedView.kt index 47ee12a6d..da425b1ff 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/RelayFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/RelayFeedView.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.relays +import android.R.attr.onClick import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -28,18 +29,20 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.note.RelayCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory import com.vitorpamplona.amethyst.ui.theme.DividerThickness +import kotlinx.coroutines.launch @Composable fun RelayFeedView( @@ -95,12 +98,15 @@ private fun RenderRelayRow( nav: INav, ) { val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() RelayCompose( relay, accountViewModel = accountViewModel, onAddRelay = { - clipboardManager.setText(AnnotatedString(relay.url.url)) - nav.nav(Route.EditRelays) + scope.launch { + clipboardManager.setText(relay.url.url) + nav.nav(Route.EditRelays) + } }, onRemoveRelay = { nav.nav(Route.EditRelays) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt index b21c17879..567246686 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt @@ -32,13 +32,14 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.text.AnnotatedString import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.nip11RelayInfo.Nip11CachedRetriever import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon import com.vitorpamplona.amethyst.ui.note.UserPicture @@ -52,6 +53,7 @@ import com.vitorpamplona.amethyst.ui.theme.LargeRelayIconModifier import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeightChatMaxWidth import com.vitorpamplona.amethyst.ui.theme.Size25dp import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl +import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable @@ -68,13 +70,16 @@ fun BasicRelaySetupInfoClickableRow( nav: INav, ) { val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() Column( Modifier .fillMaxWidth() .combinedClickable( onClick = onClick, onLongClick = { - clipboardManager.setText(AnnotatedString(item.relay.url)) + scope.launch { + clipboardManager.setText(item.relay.url) + } }, ), ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt index a7c704fff..aedd53255 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt @@ -33,18 +33,20 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.components.util.setText import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.LightRedColor import com.vitorpamplona.amethyst.ui.theme.allGoodColor import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl +import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable @@ -55,6 +57,7 @@ fun RelayNameAndRemoveButton( modifier: Modifier, ) { val clipboardManager = LocalClipboard.current + val scope = rememberCoroutineScope() Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) { Row(Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) { Text( @@ -63,7 +66,9 @@ fun RelayNameAndRemoveButton( Modifier.combinedClickable( onClick = onClick, onLongClick = { - clipboardManager.setText(AnnotatedString(item.relay.url)) + scope.launch { + clipboardManager.setText(item.relay.url) + } }, ), maxLines = 1, diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ClipboardExt.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ClipboardExt.kt new file mode 100644 index 000000000..6221c2d0d --- /dev/null +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ClipboardExt.kt @@ -0,0 +1,43 @@ +/* + * 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.desktop + +import androidx.compose.ui.platform.ClipEntry +import androidx.compose.ui.platform.Clipboard +import java.awt.datatransfer.DataFlavor +import java.awt.datatransfer.StringSelection +import java.awt.datatransfer.Transferable + +@OptIn(androidx.compose.ui.ExperimentalComposeUiApi::class) +suspend fun Clipboard.setText(text: String) { + setClipEntry(ClipEntry(StringSelection(text))) +} + +@OptIn(androidx.compose.ui.ExperimentalComposeUiApi::class) +suspend fun Clipboard.getText(): String? { + val entry = getClipEntry() ?: return null + val transferable = entry.nativeClipEntry as? Transferable ?: return null + return try { + transferable.getTransferData(DataFlavor.stringFlavor) as? String + } catch (_: Exception) { + null + } +} diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleReaderScreen.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleReaderScreen.kt index a37d8c896..052e3b910 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleReaderScreen.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/ArticleReaderScreen.kt @@ -80,6 +80,7 @@ import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.components.LoadingState import com.vitorpamplona.amethyst.desktop.account.AccountState import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache +import com.vitorpamplona.amethyst.desktop.getText import com.vitorpamplona.amethyst.desktop.network.DesktopRelayConnectionManager import com.vitorpamplona.amethyst.desktop.service.highlights.DesktopHighlightStore import com.vitorpamplona.amethyst.desktop.subscriptions.DesktopRelaySubscriptionsCoordinator @@ -99,6 +100,7 @@ import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import kotlinx.coroutines.launch +import java.awt.SystemColor.text import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter @@ -573,19 +575,26 @@ fun ArticleReaderScreen( ) { HighlightContextMenuRepresentation( delegate = defaultRepresentation, - clipboardManager = clipboardManager, - onHighlight = { text -> + onHighlight = { scope.launch { - highlightStore?.addHighlight( - articleAddressTag = addressTag, - text = text, - note = null, - articleTitle = title, - ) + val text = clipboardManager.getText() + if (!text.isNullOrBlank()) { + highlightStore?.addHighlight( + articleAddressTag = addressTag, + text = text, + note = null, + articleTitle = title, + ) + } } }, - onHighlightWithNote = { text -> - showAnnotationDialog = text + onHighlightWithNote = { + scope.launch { + val text = clipboardManager.getText() + if (!text.isNullOrBlank()) { + showAnnotationDialog = text + } + } }, ) } @@ -695,9 +704,8 @@ fun ArticleReaderScreen( */ private class HighlightContextMenuRepresentation( private val delegate: ContextMenuRepresentation, - private val clipboardManager: androidx.compose.ui.platform.ClipboardManager, - private val onHighlight: (String) -> Unit, - private val onHighlightWithNote: (String) -> Unit, + private val onHighlight: () -> Unit, + private val onHighlightWithNote: () -> Unit, ) : ContextMenuRepresentation { @Composable override fun Representation( @@ -713,17 +721,11 @@ private class HighlightContextMenuRepresentation( listOf( ContextMenuItem("Highlight") { copyItem.onClick() - val text = clipboardManager.getText()?.text - if (!text.isNullOrBlank()) { - onHighlight(text) - } + onHighlight() }, ContextMenuItem("Highlight with Note") { copyItem.onClick() - val text = clipboardManager.getText()?.text - if (!text.isNullOrBlank()) { - onHighlightWithNote(text) - } + onHighlightWithNote() }, ) } else { diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/auth/LoginCard.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/auth/LoginCard.kt index f5bd7b9e3..9ce7b7167 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/auth/LoginCard.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/auth/LoginCard.kt @@ -50,7 +50,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -63,6 +62,7 @@ import com.vitorpamplona.amethyst.commons.resources.login_card_title import com.vitorpamplona.amethyst.commons.resources.login_generate_button import com.vitorpamplona.amethyst.desktop.account.LoginProgress import com.vitorpamplona.amethyst.desktop.account.validateBunkerUri +import com.vitorpamplona.amethyst.desktop.setText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -318,7 +318,11 @@ private fun NostrConnectContent( Spacer(Modifier.height(8.dp)) OutlinedButton( - onClick = { clipboardManager.setText(AnnotatedString(uri)) }, + onClick = { + scope.launch { + clipboardManager.setText(uri) + } + }, modifier = Modifier.fillMaxWidth(), ) { Text("Copy URI")