refactor(note): route the note tree's platform pieces through NotePlatform

The NoteCompose tree now reaches Android-only code only through LocalNotePlatform:
- same-named shims in ui.note.platform (with the originals' defaults) front the zoomable
  viewer, video/GIF players, link previews and their state, the map and reverse geocoding,
  the audio, chess, git, meeting-room and napplet/nsite note types, the reactions/zap row
  and the post editor; tree files only switched imports
- TranslationPlatform gains cachedTranslation, so "Copy Translated" no longer calls the
  flavour's cache directly
- RenderReaction and the audio-meta helpers leave ReactionsRow/VoiceTrack for their own
  files, since shared code uses them without the players

Measured closure of NoteCompose: 176 files, no Android-only exits left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P
This commit is contained in:
Claude
2026-09-28 19:12:11 +00:00
parent 83add4d355
commit 44432ffc0f
39 changed files with 224 additions and 133 deletions
@@ -46,6 +46,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size6dp
import com.vitorpamplona.amethyst.commons.ui.theme.Size75dp
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
import com.vitorpamplona.amethyst.ui.note.platform.GifVideoView
@Composable
fun MyAsyncImage(
@@ -32,13 +32,13 @@ import com.vitorpamplona.amethyst.commons.resources.Res
import com.vitorpamplona.amethyst.commons.resources.copy_text
import com.vitorpamplona.amethyst.commons.resources.copy_text_original
import com.vitorpamplona.amethyst.commons.resources.copy_text_translated
import com.vitorpamplona.amethyst.commons.ui.components.LocalTranslationPlatform
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionDialog
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionRow
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionSection
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.components.cachedTranslation
import com.vitorpamplona.amethyst.ui.note.types.displayedNoteText
import kotlinx.coroutines.launch
@@ -80,6 +80,7 @@ fun copyNoteTextAction(
onDismiss: () -> Unit,
): (note: Note, versionShown: Note) -> Unit {
val clipboardManager = LocalClipboard.current
val translator = LocalTranslationPlatform.current
val scope = rememberCoroutineScope()
val choice = remember { mutableStateOf<CopyTextChoice?>(null) }
@@ -113,7 +114,7 @@ fun copyNoteTextAction(
onDismiss()
} else {
val original = displayedNoteText(note, decrypted)
val translated = cachedTranslation(original, accountViewModel)
val translated = translator.cachedTranslation(original, accountViewModel)
if (translated == null) {
copy(original)
} else {
@@ -145,6 +145,25 @@ import com.vitorpamplona.amethyst.ui.note.elements.DisplayReward
import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton
import com.vitorpamplona.amethyst.ui.note.elements.Reward
import com.vitorpamplona.amethyst.ui.note.elements.ShowForkInformation
import com.vitorpamplona.amethyst.ui.note.platform.ReactionsRow
import com.vitorpamplona.amethyst.ui.note.platform.RenderAudioHeader
import com.vitorpamplona.amethyst.ui.note.platform.RenderAudioTrack
import com.vitorpamplona.amethyst.ui.note.platform.RenderChessGame
import com.vitorpamplona.amethyst.ui.note.platform.RenderGitIssueEvent
import com.vitorpamplona.amethyst.ui.note.platform.RenderGitPatchEvent
import com.vitorpamplona.amethyst.ui.note.platform.RenderGitPullRequestEvent
import com.vitorpamplona.amethyst.ui.note.platform.RenderGitPullRequestUpdateEvent
import com.vitorpamplona.amethyst.ui.note.platform.RenderGitRepositoryEvent
import com.vitorpamplona.amethyst.ui.note.platform.RenderLiveChessChallenge
import com.vitorpamplona.amethyst.ui.note.platform.RenderLiveChessGameEnd
import com.vitorpamplona.amethyst.ui.note.platform.RenderMeetingRoomEvent
import com.vitorpamplona.amethyst.ui.note.platform.RenderMeetingSpaceEvent
import com.vitorpamplona.amethyst.ui.note.platform.RenderMusicTrack
import com.vitorpamplona.amethyst.ui.note.platform.RenderNamedNappletEvent
import com.vitorpamplona.amethyst.ui.note.platform.RenderNamedSiteEvent
import com.vitorpamplona.amethyst.ui.note.platform.RenderRootNappletEvent
import com.vitorpamplona.amethyst.ui.note.platform.RenderRootSiteEvent
import com.vitorpamplona.amethyst.ui.note.platform.RenderVoiceTrack
import com.vitorpamplona.amethyst.ui.note.types.BadgeDisplay
import com.vitorpamplona.amethyst.ui.note.types.DisplayBlockedRelayList
import com.vitorpamplona.amethyst.ui.note.types.DisplayBroadcastRelayList
@@ -170,8 +189,6 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderAttestation
import com.vitorpamplona.amethyst.ui.note.types.RenderAttestationRequest
import com.vitorpamplona.amethyst.ui.note.types.RenderAttestorProficiency
import com.vitorpamplona.amethyst.ui.note.types.RenderAttestorRecommendation
import com.vitorpamplona.amethyst.ui.note.types.RenderAudioHeader
import com.vitorpamplona.amethyst.ui.note.types.RenderAudioTrack
import com.vitorpamplona.amethyst.ui.note.types.RenderBadgeAward
import com.vitorpamplona.amethyst.ui.note.types.RenderBlossomPieceIndex
import com.vitorpamplona.amethyst.ui.note.types.RenderBolt12Zap
@@ -185,7 +202,6 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderChannelMessage
import com.vitorpamplona.amethyst.ui.note.types.RenderChat
import com.vitorpamplona.amethyst.ui.note.types.RenderChatMessage
import com.vitorpamplona.amethyst.ui.note.types.RenderChatMessageEncryptedFile
import com.vitorpamplona.amethyst.ui.note.types.RenderChessGame
import com.vitorpamplona.amethyst.ui.note.types.RenderCitation
import com.vitorpamplona.amethyst.ui.note.types.RenderClassifieds
import com.vitorpamplona.amethyst.ui.note.types.RenderCommunity
@@ -199,26 +215,14 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderFhirResource
import com.vitorpamplona.amethyst.ui.note.types.RenderFundraiser
import com.vitorpamplona.amethyst.ui.note.types.RenderGeocache
import com.vitorpamplona.amethyst.ui.note.types.RenderGeocacheFoundLog
import com.vitorpamplona.amethyst.ui.note.types.RenderGitIssueEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderGitPatchEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderGitPullRequestEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderGitPullRequestUpdateEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderGitRepositoryEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderGoal
import com.vitorpamplona.amethyst.ui.note.types.RenderHighlight
import com.vitorpamplona.amethyst.ui.note.types.RenderInteractiveStory
import com.vitorpamplona.amethyst.ui.note.types.RenderLearningResource
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityChatMessage
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveActivityEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveChessChallenge
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveChessGameEnd
import com.vitorpamplona.amethyst.ui.note.types.RenderLongFormContent
import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingRoomEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingSpaceEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderMusicPlaylist
import com.vitorpamplona.amethyst.ui.note.types.RenderMusicTrack
import com.vitorpamplona.amethyst.ui.note.types.RenderNamedNappletEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderNamedSiteEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderNipContent
import com.vitorpamplona.amethyst.ui.note.types.RenderNutzap
import com.vitorpamplona.amethyst.ui.note.types.RenderOnchainZap
@@ -245,8 +249,6 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderRelayReview
import com.vitorpamplona.amethyst.ui.note.types.RenderReport
import com.vitorpamplona.amethyst.ui.note.types.RenderRoadEventConfirmation
import com.vitorpamplona.amethyst.ui.note.types.RenderRoadEventReport
import com.vitorpamplona.amethyst.ui.note.types.RenderRootNappletEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderRootSiteEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderSnoAvatar
import com.vitorpamplona.amethyst.ui.note.types.RenderSnoObject
import com.vitorpamplona.amethyst.ui.note.types.RenderSnoShard
@@ -260,7 +262,6 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderTorrent
import com.vitorpamplona.amethyst.ui.note.types.RenderTorrentComment
import com.vitorpamplona.amethyst.ui.note.types.RenderVideoCollaboration
import com.vitorpamplona.amethyst.ui.note.types.RenderVideoCurationSet
import com.vitorpamplona.amethyst.ui.note.types.RenderVoiceTrack
import com.vitorpamplona.amethyst.ui.note.types.RenderWikiContent
import com.vitorpamplona.amethyst.ui.note.types.RenderWikiMergeAcceptance
import com.vitorpamplona.amethyst.ui.note.types.RenderWikiMergeRequest
@@ -109,7 +109,6 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -2061,55 +2060,6 @@ private fun ActionableReactionButton(
}
}
@Composable
fun RenderReaction(reactionType: String) {
if (reactionType.startsWith(":")) {
val noStartColon = reactionType.removePrefix(":")
val url = noStartColon.substringAfter(":")
InLineIconRenderer(
persistentListOf(
CustomEmoji.ImageUrlType(url),
),
style = SpanStyle(color = MaterialTheme.colorScheme.onBackground),
maxLines = 1,
fontSize = 22.sp,
)
} else {
when (reactionType) {
"+" -> {
LikedIcon(modifier = Size28Modifier)
}
"-" -> {
Text(
text = "\uD83D\uDC4E",
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
fontSize = 22.sp,
)
}
else -> {
if (EmojiCoder.isCoded(reactionType)) {
AnimatedBorderTextCornerRadius(
reactionType,
color = MaterialTheme.colorScheme.onBackground,
fontSize = 20.sp,
)
} else {
Text(
reactionType,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
fontSize = 22.sp,
)
}
}
}
}
}
@Composable
fun ZapAmountChoicePopup(
baseNote: Note,
@@ -0,0 +1,83 @@
/*
* 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.note
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
import com.vitorpamplona.amethyst.commons.ui.components.AnimatedBorderTextCornerRadius
import com.vitorpamplona.amethyst.commons.ui.note.LikedIcon
import com.vitorpamplona.amethyst.commons.ui.richtext.InLineIconRenderer
import com.vitorpamplona.amethyst.commons.ui.theme.Size28Modifier
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
import kotlinx.collections.immutable.persistentListOf
@Composable
fun RenderReaction(reactionType: String) {
if (reactionType.startsWith(":")) {
val noStartColon = reactionType.removePrefix(":")
val url = noStartColon.substringAfter(":")
InLineIconRenderer(
persistentListOf(
CustomEmoji.ImageUrlType(url),
),
style = SpanStyle(color = MaterialTheme.colorScheme.onBackground),
maxLines = 1,
fontSize = 22.sp,
)
} else {
when (reactionType) {
"+" -> {
LikedIcon(modifier = Size28Modifier)
}
"-" -> {
Text(
text = "\uD83D\uDC4E",
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
fontSize = 22.sp,
)
}
else -> {
if (EmojiCoder.isCoded(reactionType)) {
AnimatedBorderTextCornerRadius(
reactionType,
color = MaterialTheme.colorScheme.onBackground,
fontSize = 20.sp,
)
} else {
Text(
reactionType,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
fontSize = 22.sp,
)
}
}
}
}
}
@@ -29,7 +29,7 @@ import com.vitorpamplona.amethyst.commons.model.navigation.Route
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.note.HeaderPill
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName
import com.vitorpamplona.amethyst.ui.note.platform.LoadCityName
/**
* Compact pill showing the geohash a note is scoped to, resolved to a city
@@ -45,9 +45,9 @@ import com.vitorpamplona.amethyst.commons.ui.note.VerticalDotsIcon
import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.commons.ui.theme.Size24Modifier
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.actions.EditPostView
import com.vitorpamplona.amethyst.ui.note.QuickActionAlertDialog
import com.vitorpamplona.amethyst.ui.note.deletionRequestBody
import com.vitorpamplona.amethyst.ui.note.platform.EditPostView
import com.vitorpamplona.amethyst.ui.note.types.EditState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.report.ReportNoteDialog
import com.vitorpamplona.quartz.nip01Core.tags.aTag.isTaggedAddressableNote
@@ -52,7 +52,7 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.components.rememberUrlPreviewState
import com.vitorpamplona.amethyst.ui.note.platform.rememberUrlPreviewState
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
import com.vitorpamplona.quartz.nip73ExternalIds.topics.HashtagId
@@ -46,7 +46,7 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName
import com.vitorpamplona.amethyst.ui.note.platform.LoadCityName
import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
@Composable
@@ -33,6 +33,7 @@ import com.vitorpamplona.amethyst.commons.audio.WaveformData
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
import com.vitorpamplona.amethyst.commons.ui.components.UrlPreviewState
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.note.types.EditState
@@ -45,6 +46,7 @@ import com.vitorpamplona.amethyst.ui.components.GifVideoView as AppGifVideoView
import com.vitorpamplona.amethyst.ui.components.LoadUrlPreview as AppLoadUrlPreview
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView as AppZoomableContentView
import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog as AppZoomableImageDialog
import com.vitorpamplona.amethyst.ui.components.rememberUrlPreviewState as AppRememberUrlPreviewState
import com.vitorpamplona.amethyst.ui.note.LikeReaction as AppLikeReaction
import com.vitorpamplona.amethyst.ui.note.ReactionsRow as AppReactionsRow
import com.vitorpamplona.amethyst.ui.note.ZapReaction as AppZapReaction
@@ -52,6 +54,7 @@ import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName as AppL
import com.vitorpamplona.amethyst.ui.note.creators.location.LocationPreviewMap as AppLocationPreviewMap
import com.vitorpamplona.amethyst.ui.note.types.PodcastEpisodeAudioPlayer as AppPodcastEpisodeAudioPlayer
import com.vitorpamplona.amethyst.ui.note.types.RenderAudioFromIMeta as AppRenderAudioFromIMeta
import com.vitorpamplona.amethyst.ui.note.types.RenderAudioHeader as AppRenderAudioHeader
import com.vitorpamplona.amethyst.ui.note.types.RenderAudioTrack as AppRenderAudioTrack
import com.vitorpamplona.amethyst.ui.note.types.RenderAudioWithWaveform as AppRenderAudioWithWaveform
import com.vitorpamplona.amethyst.ui.note.types.RenderChessGame as AppRenderChessGame
@@ -74,6 +77,20 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderVoiceTrack as AppRenderVoi
/** Android's [NotePlatform]: the app's own media, map, audio, chess, git, napplet and zap composables. */
object AndroidNotePlatform : NotePlatform {
@Composable
override fun rememberUrlPreviewState(
url: String,
accountViewModel: AccountViewModel,
): UrlPreviewState = AppRememberUrlPreviewState(url, accountViewModel)
@Composable
override fun RenderAudioHeader(
note: Note,
contentScale: ContentScale,
accountViewModel: AccountViewModel,
nav: INav,
) = AppRenderAudioHeader(note, contentScale, accountViewModel, nav)
@Composable
override fun ZoomableContentView(
content: BaseMediaContent,
@@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.commons.audio.WaveformData
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
import com.vitorpamplona.amethyst.commons.ui.components.UrlPreviewState
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.note.types.EditState
@@ -111,6 +112,13 @@ interface NotePlatform {
nav: INav?,
) {}
/** The OpenGraph preview of [url], starting from the platform's cache. */
@Composable
fun rememberUrlPreviewState(
url: String,
accountViewModel: AccountViewModel,
): UrlPreviewState = UrlPreviewState.Loading
// Places
@Composable
@@ -133,6 +141,14 @@ interface NotePlatform {
// Audio
@Composable
fun RenderAudioHeader(
note: Note,
contentScale: ContentScale,
accountViewModel: AccountViewModel,
nav: INav,
) {}
@Composable
fun RenderAudioTrack(
note: Note,
@@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.commons.audio.WaveformData
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
import com.vitorpamplona.amethyst.commons.ui.components.UrlPreviewState
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.theme.Font14SP
import com.vitorpamplona.amethyst.commons.ui.theme.Size14Modifier
@@ -536,3 +537,17 @@ fun EditPostView(
accountViewModel = accountViewModel,
nav = nav,
)
@Composable
fun rememberUrlPreviewState(
url: String,
accountViewModel: AccountViewModel,
): UrlPreviewState = LocalNotePlatform.current.rememberUrlPreviewState(url, accountViewModel)
@Composable
fun RenderAudioHeader(
note: Note,
contentScale: ContentScale,
accountViewModel: AccountViewModel,
nav: INav,
) = LocalNotePlatform.current.RenderAudioHeader(note, contentScale, accountViewModel, nav)
@@ -117,7 +117,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size16Modifier
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog
import com.vitorpamplona.amethyst.ui.note.platform.ZoomableImageDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.KindChip
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.kindDisplayName
import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.SoftwareApplicationEvent
@@ -18,15 +18,28 @@
* 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
package com.vitorpamplona.amethyst.ui.note.types
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip92IMeta.imetas
import com.vitorpamplona.quartz.nipA0VoiceMessages.AudioMeta
/**
* No translation service in this flavor, so no note is ever translated and the copy-text
* menus never need to offer a "Copy Translated" option.
* Extracts AudioMeta from an event's IMeta tags if it has audio content with waveform.
* Returns the first audio IMeta that has a waveform, or null if none found.
*/
fun cachedTranslation(
content: String,
accountViewModel: AccountViewModel,
): String? = null
fun Event.getAudioMetaWithWaveform(): AudioMeta? {
val audioMetas = imetas().map { AudioMeta.parse(it) }
return audioMetas.firstOrNull { meta ->
meta.waveform != null &&
(meta.mimeType == null || meta.mimeType?.startsWith("audio/") == true)
}
}
/**
* Checks if the event content is primarily an audio attachment (content is just the audio URL).
*/
fun Event.isAudioOnlyContent(): Boolean {
val audioMeta = getAudioMetaWithWaveform() ?: return false
return content.trim() == audioMeta.url
}
@@ -48,8 +48,8 @@ import com.vitorpamplona.amethyst.commons.ui.theme.subtleBorder
import com.vitorpamplona.amethyst.commons.util.showAmount
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.components.AutoNonlazyGrid
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader
import com.vitorpamplona.amethyst.ui.note.platform.ZoomableContentView
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
import com.vitorpamplona.quartz.utils.parseBigDecimalOrNull
import kotlinx.collections.immutable.ImmutableList
@@ -103,12 +103,12 @@ import com.vitorpamplona.amethyst.commons.util.equalImmutableLists
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.note.LikeReaction
import com.vitorpamplona.amethyst.ui.note.ZapReaction
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeaderBackground
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
import com.vitorpamplona.amethyst.ui.note.externalLinkForNote
import com.vitorpamplona.amethyst.ui.note.platform.LikeReaction
import com.vitorpamplona.amethyst.ui.note.platform.ZapReaction
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.ModeratorTag
@@ -33,7 +33,7 @@ import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.commons.ui.components.FileAttachmentCard
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
import com.vitorpamplona.amethyst.ui.note.platform.ZoomableContentView
import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.nip94FileMetadata.FileMetadataEvent
@@ -35,7 +35,7 @@ import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
import com.vitorpamplona.amethyst.commons.ui.components.LoadNote
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
import com.vitorpamplona.amethyst.ui.note.platform.ZoomableContentView
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
import com.vitorpamplona.quartz.nip31Alts.alt
@@ -36,7 +36,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.GeocacheFoundLogCard
import com.vitorpamplona.amethyst.commons.ui.note.LoadAddressableNote
import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.note.creators.location.LocationPreviewMap
import com.vitorpamplona.amethyst.ui.note.platform.LocationPreviewMap
import com.vitorpamplona.quartz.nip01Core.tags.geohash.toGeoHash
import com.vitorpamplona.quartz.nipCCGeocaching.foundLog.GeocacheFoundLogEvent
import com.vitorpamplona.quartz.nipCCGeocaching.listing.GeocacheListingEvent
@@ -80,8 +80,8 @@ import com.vitorpamplona.amethyst.commons.util.countToHumanReadableBytes
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
import com.vitorpamplona.amethyst.ui.note.platform.ZoomableContentView
import com.vitorpamplona.quartz.experimental.library.BlossomPieceIndexEvent
import com.vitorpamplona.quartz.experimental.library.BookshelfDirectoryEvent
import com.vitorpamplona.quartz.experimental.library.LearningResourceEvent
@@ -72,11 +72,11 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
import com.vitorpamplona.amethyst.commons.util.equalImmutableLists
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
import com.vitorpamplona.amethyst.service.playback.composable.VideoView
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeaderBackground
import com.vitorpamplona.amethyst.ui.note.platform.VideoView
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTag
@@ -47,9 +47,9 @@ import com.vitorpamplona.amethyst.ui.components.BlurhashBackdrop
import com.vitorpamplona.amethyst.ui.components.BlurhashGridBackdrop
import com.vitorpamplona.amethyst.ui.components.ContentWarningGate
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
import com.vitorpamplona.amethyst.ui.components.collectContentWarningReasons
import com.vitorpamplona.amethyst.ui.components.mediaSizingModifier
import com.vitorpamplona.amethyst.ui.note.platform.ZoomableContentView
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
import kotlinx.collections.immutable.toImmutableList
@@ -64,6 +64,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.note.platform.PodcastEpisodeAudioPlayer
import com.vitorpamplona.quartz.podcasts.PodcastEpisode
// Bottom-rounded border on the audio player so it visually butts up against the cover's
@@ -24,7 +24,7 @@ import androidx.compose.runtime.Composable
import com.vitorpamplona.amethyst.commons.model.Note
import com.vitorpamplona.amethyst.commons.ui.note.RoadEventConfirmationCard
import com.vitorpamplona.amethyst.commons.ui.note.RoadEventReportCard
import com.vitorpamplona.amethyst.ui.note.creators.location.LocationPreviewMap
import com.vitorpamplona.amethyst.ui.note.platform.LocationPreviewMap
import com.vitorpamplona.quartz.experimental.roadstr.confirmation.RoadEventConfirmationEvent
import com.vitorpamplona.quartz.experimental.roadstr.report.RoadEventReportEvent
@@ -92,9 +92,9 @@ import com.vitorpamplona.amethyst.commons.util.DecimalPatternFormatter
import com.vitorpamplona.amethyst.commons.util.prettyMime
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
import com.vitorpamplona.amethyst.ui.note.ReactionsRow
import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton
import com.vitorpamplona.amethyst.ui.note.platform.ReactionsRow
import com.vitorpamplona.amethyst.ui.note.platform.ZoomableContentView
import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.SoftwareApplicationEvent
import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.asset.SoftwareAssetEvent
import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.release.isNip82SoftwareRelease
@@ -53,6 +53,7 @@ import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.note.ReplyNoteComposition
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
import com.vitorpamplona.amethyst.ui.note.nip22Comments.DisplayCommentScope
import com.vitorpamplona.amethyst.ui.note.platform.RenderAudioFromIMeta
import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.PreloadThreadForReply
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
import com.vitorpamplona.quartz.nip01Core.tags.people.hasAnyTaggedUser
@@ -61,10 +61,10 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier
import com.vitorpamplona.amethyst.commons.ui.theme.innerPostModifier
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.actions.EditPostView
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.note.NoteBody
import com.vitorpamplona.amethyst.ui.note.observeEdits
import com.vitorpamplona.amethyst.ui.note.platform.EditPostView
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
import kotlinx.collections.immutable.persistentListOf
@@ -58,10 +58,10 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeaderBackground
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
import com.vitorpamplona.amethyst.ui.note.platform.ZoomableContentView
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
import com.vitorpamplona.quartz.nip31Alts.alt
@@ -79,11 +79,8 @@ import com.vitorpamplona.amethyst.ui.components.ShareMediaAction
import com.vitorpamplona.amethyst.ui.components.getActivity
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
import com.vitorpamplona.amethyst.ui.screen.loggedIn.saveMediaToGallery
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
import com.vitorpamplona.quartz.nip14Subject.subject
import com.vitorpamplona.quartz.nip92IMeta.imetas
import com.vitorpamplona.quartz.nipA0VoiceMessages.AudioMeta
import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent
@Composable
@@ -321,26 +318,6 @@ fun PlayPauseButton(controllerState: MediaControllerState) {
}
}
/**
* Extracts AudioMeta from an event's IMeta tags if it has audio content with waveform.
* Returns the first audio IMeta that has a waveform, or null if none found.
*/
fun Event.getAudioMetaWithWaveform(): AudioMeta? {
val audioMetas = imetas().map { AudioMeta.parse(it) }
return audioMetas.firstOrNull { meta ->
meta.waveform != null &&
(meta.mimeType == null || meta.mimeType?.startsWith("audio/") == true)
}
}
/**
* Checks if the event content is primarily an audio attachment (content is just the audio URL).
*/
fun Event.isAudioOnlyContent(): Boolean {
val audioMeta = getAudioMetaWithWaveform() ?: return false
return content.trim() == audioMeta.url
}
/**
* Renders audio with waveform for any event type that has audio IMeta attachment.
* This allows KIND 1 (TextNoteEvent) and KIND 1111 (CommentEvent) with voice
@@ -46,8 +46,8 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.commons.ui.theme.Size20dp
import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.service.playback.composable.VideoView
import com.vitorpamplona.amethyst.ui.note.CrossfadeToDisplayComment
import com.vitorpamplona.amethyst.ui.note.platform.VideoView
import com.vitorpamplona.quartz.nip53LiveActivities.clip.LiveActivitiesClipEvent
@Composable
@@ -41,8 +41,8 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
import com.vitorpamplona.amethyst.ui.note.types.RenderAudioWithWaveform
import com.vitorpamplona.amethyst.ui.note.platform.RenderAudioWithWaveform
import com.vitorpamplona.amethyst.ui.note.platform.ZoomableContentView
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.utils.ciphers.AESGCM
@@ -53,8 +53,8 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp
import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
import com.vitorpamplona.amethyst.ui.note.LikeReaction
import com.vitorpamplona.amethyst.ui.note.ZapReaction
import com.vitorpamplona.amethyst.ui.note.platform.LikeReaction
import com.vitorpamplona.amethyst.ui.note.platform.ZapReaction
@Composable
fun ShortPublicChatChannelHeader(
@@ -34,7 +34,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StreamingHeaderModifier
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannelInfo
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
import com.vitorpamplona.amethyst.ui.note.platform.ZoomableContentView
@Composable
fun ShowVideoStreaming(
@@ -50,8 +50,8 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp
import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
import com.vitorpamplona.amethyst.ui.note.LikeReaction
import com.vitorpamplona.amethyst.ui.note.ZapReaction
import com.vitorpamplona.amethyst.ui.note.platform.LikeReaction
import com.vitorpamplona.amethyst.ui.note.platform.ZapReaction
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
@Composable
@@ -58,10 +58,10 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
import com.vitorpamplona.amethyst.ui.note.GalleryUnloaded
import com.vitorpamplona.amethyst.ui.note.LikeReaction
import com.vitorpamplona.amethyst.ui.note.ZapReaction
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageBanner
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageBannerBackground
import com.vitorpamplona.amethyst.ui.note.platform.LikeReaction
import com.vitorpamplona.amethyst.ui.note.platform.ZapReaction
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
import kotlinx.collections.immutable.ImmutableList
@@ -48,7 +48,7 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes
import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp
import com.vitorpamplona.amethyst.commons.ui.theme.grayText
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
import com.vitorpamplona.amethyst.ui.note.types.PodcastEpisodeAudioPlayer
import com.vitorpamplona.amethyst.ui.note.platform.PodcastEpisodeAudioPlayer
import com.vitorpamplona.quartz.nipXXPodcasting20.trailer.Podcasting20TrailerEvent
import com.vitorpamplona.quartz.podcasts.PodcastAudio
@@ -28,9 +28,9 @@ import com.vitorpamplona.amethyst.service.lang.TranslationsCache
* when no translation occurred (same language, undetected source, blocklisted) or none is
* cached. Cache-only on purpose: this backs the "Copy Translated" option of the copy-text
* menus, which only applies to text the user is looking at — and rendering it through
* [TranslatableRichTextViewer] is what populated the cache.
* [MlKitTranslationPlatform] is what populated the cache.
*/
fun cachedTranslation(
internal fun cachedMlKitTranslation(
content: String,
accountViewModel: AccountViewModel,
): String? {
@@ -59,6 +59,11 @@ object MlKitTranslationPlatform : TranslationPlatform {
content: String,
accountViewModel: AccountViewModel,
): String = rememberMlKitTranslation(content, accountViewModel)
override fun cachedTranslation(
content: String,
accountViewModel: AccountViewModel,
): String? = cachedMlKitTranslation(content, accountViewModel)
}
val FlavorTranslationPlatform: TranslationPlatform = MlKitTranslationPlatform
@@ -53,6 +53,16 @@ interface TranslationPlatform {
accountViewModel: AccountViewModel,
): String
/**
* The already-computed translation of [content] under the current settings, or null when
* none occurred or none is cached. Cache-only: it backs "Copy Translated", which applies
* to text on screen, and rendering that text through [Translatable] filled the cache.
*/
fun cachedTranslation(
content: String,
accountViewModel: AccountViewModel,
): String? = null
/** No translator: the content is always its own translation. */
object None : TranslationPlatform {
@Composable