mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
refactor(note): cut JVM/Android ties out of the NoteCompose tree; translation and note slots
KMP swaps so the note types can live in commonMain: - formatDateTime(DateTimeStyle) replaces SimpleDateFormat in NoteCompose, DisplayOts, CalendarEvent, Attestation, MeetingSpace and the live-stream flags - DecimalPatternFormatter, PlatformNumberFormatter and the quartz BigDecimal (plus parseBigDecimalOrNull) replace java.text / java.math in the zap, goal, reward, poll, medical, classifieds and software-app types - phonePrefersMiles() is a commons expect; timeDiffAgo* and formatSecondsToTime are common - UrlInfoItem resolves OpenGraph URLs through resolveHttpUrl and moves to commonMain with the URL preview card, recording indicators and streaming flags - NIP-95 local media is an okio Path from Nip95BlobStore.path(id) - ic_dm, nip_05 and profile_banner move to commonsUI Compose resources (painterRes) - podcast chapters/transcripts fetch through fetchPreviewText; live streams probe through isVideoOnline; the event "copy JSON" goes through toPrettyJson Platform verbs as small commonsUI expect/actuals: rememberTextSharer (share sheet), rememberShortNotice (toast), rememberBlossomUriOpener, argbPixelsToImageBitmap. Plain VIEW intents use LocalUriHandler; the zap poll pays through AccountViewModelHost. Slots: TranslationPlatform (Play installs ML Kit, F-Droid none) behind a shared TranslatableRichTextViewer; RichTextPlatform gains SecretMessage; the preview AccountViewModel mocks move to commonsUI. NotePlatform (media players, map, audio/chess/ git/meeting/napplet note types, reactions row, post editor) is defined with its Android implementation installed in the theme; call sites are switched in the next step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P
This commit is contained in:
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.TranslationPlatform
|
||||
|
||||
/** No translation service in this flavor, so the content is always its own "translation". */
|
||||
val FlavorTranslationPlatform: TranslationPlatform = TranslationPlatform.None
|
||||
+1
-6
@@ -30,13 +30,13 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.model.nip71Video.CaptionTrack
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.DownloadForOfflineIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size75dp
|
||||
@@ -50,11 +50,6 @@ import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Immutable
|
||||
class WaveformData(
|
||||
val wave: List<Float>,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun VideoView(
|
||||
videoUri: String,
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.model.nip71Video.CaptionTrack
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mainVideo.VideoPlayerActiveMutex
|
||||
|
||||
+1
-1
@@ -73,6 +73,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size50Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.service.cast.CastRequest
|
||||
import com.vitorpamplona.amethyst.service.cast.CastSessionState
|
||||
import com.vitorpamplona.amethyst.service.cast.resolveCastLiveness
|
||||
@@ -86,7 +87,6 @@ import com.vitorpamplona.amethyst.ui.cast.CastDevicePickerDialog
|
||||
import com.vitorpamplona.amethyst.ui.cast.rememberCastWithLocalNetworkPermission
|
||||
import com.vitorpamplona.amethyst.ui.components.ShareMediaAction
|
||||
import com.vitorpamplona.amethyst.ui.components.getActivity
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.saveMediaToGallery
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
+1
-1
@@ -25,9 +25,9 @@ import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.model.nip71Video.CaptionTrack
|
||||
import com.vitorpamplona.amethyst.commons.ui.state.produceCachedState
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
+1
-1
@@ -22,8 +22,8 @@ package com.vitorpamplona.amethyst.service.playback.composable.mediaitem
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.media3.common.MediaItem
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.model.nip71Video.CaptionTrack
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
+1
-1
@@ -44,9 +44,9 @@ import androidx.media3.common.Tracks
|
||||
import com.vitorpamplona.amethyst.commons.audio.AudioVisualizer
|
||||
import com.vitorpamplona.amethyst.commons.audio.Spectrum
|
||||
import com.vitorpamplona.amethyst.commons.audio.VisualizerStyle
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.audio.delayedByFrames
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.service.playback.playerPool.PcmTapRegistry
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
|
||||
|
||||
+4
-3
@@ -20,12 +20,13 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.playback.composable.wavefront
|
||||
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.sin
|
||||
import kotlin.random.Random
|
||||
|
||||
private const val SYNTHETIC_WAVEFORM_SAMPLES = 96
|
||||
private const val TWO_PI = (Math.PI * 2).toFloat()
|
||||
private const val TWO_PI = (PI * 2).toFloat()
|
||||
|
||||
/**
|
||||
* Builds a stable, decorative waveform from a [seed] (usually an event id). ExoPlayer drives
|
||||
@@ -57,7 +58,7 @@ fun syntheticWaveformFor(seed: String): WaveformData {
|
||||
List(SYNTHETIC_WAVEFORM_SAMPLES) { index ->
|
||||
val phase = index.toFloat() / SYNTHETIC_WAVEFORM_SAMPLES
|
||||
// Optional gentle fade so some taper at the ends, others stay even.
|
||||
val envelope = 1f - envelopeStrength * (1f - sin(phase * Math.PI).toFloat())
|
||||
val envelope = 1f - envelopeStrength * (1f - sin(phase * PI).toFloat())
|
||||
val carrier = sin(phase * TWO_PI * carrierCycles + phaseOffset) * carrierWeight
|
||||
val noise = (rng.nextFloat() - 0.5f) * 2f * noiseStrength
|
||||
((baseline + carrier + noise) * envelope).coerceIn(0.05f, 1.0f)
|
||||
|
||||
+1
-1
@@ -36,9 +36,9 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.media3.common.Player
|
||||
import com.linc.audiowaveform.infiniteLinearGradient
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.AudioWaveformReadOnly
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
|
||||
@Composable
|
||||
fun Waveform(
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.service.playback.pip
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemData
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
|
||||
+1
-1
@@ -40,10 +40,10 @@ import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.compose.ContentFrame
|
||||
import androidx.media3.ui.compose.state.rememberMuteButtonState
|
||||
import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.VoiceHeightModifier
|
||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.PIP_PRESHRINK_MAX_SHORT_SIDE_PX
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.constrainVideoQualityToViewport
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemData
|
||||
|
||||
@@ -273,4 +273,14 @@ object AndroidRichTextPlatform : RichTextPlatform {
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = RenderContentAsMarkdown(content, tags, canPreview, quotesLeft, backgroundColor, callbackUri, accountViewModel, nav)
|
||||
|
||||
@Composable
|
||||
override fun SecretMessage(
|
||||
content: RichTextViewerState,
|
||||
callbackUri: String?,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = CoreSecretMessage(content, callbackUri, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
|
||||
@@ -22,11 +22,10 @@ package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.ClickableTextPrimary
|
||||
import com.vitorpamplona.amethyst.service.uploads.blossom.bud10.openBlossomUriAsIntent
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.rememberBlossomUriOpener
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.ClickableUrl as SharedClickableUrl
|
||||
|
||||
@@ -44,13 +43,13 @@ fun ClickableUrl(
|
||||
onError: (StringResource, StringResource) -> Unit = { _, _ -> },
|
||||
) {
|
||||
if (url.startsWith("blossom:")) {
|
||||
val context = LocalContext.current
|
||||
val blossomOpener = rememberBlossomUriOpener()
|
||||
ClickableTextPrimary(
|
||||
text = urlText,
|
||||
style = style,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.MiddleEllipsis,
|
||||
onClick = { openBlossomUriAsIntent(context, url, onError) },
|
||||
onClick = { blossomOpener.open(url, onError) },
|
||||
)
|
||||
} else {
|
||||
SharedClickableUrl(url = url, displayText = urlText, style = style)
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import android.util.LruCache
|
||||
import androidx.collection.LruCache
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlContent
|
||||
import com.vitorpamplona.amethyst.commons.service.image.placeholderModel
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.rememberBlossomUriOpener
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.DownloadForOfflineIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size20dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size24dp
|
||||
import kotlinx.coroutines.delay
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
|
||||
@Composable
|
||||
fun ImageUrlWithDownloadButton(
|
||||
url: String,
|
||||
showImage: MutableState<Boolean>,
|
||||
) {
|
||||
val uri = LocalUriHandler.current
|
||||
|
||||
val primary = MaterialTheme.colorScheme.primary
|
||||
val background = MaterialTheme.colorScheme.onBackground
|
||||
|
||||
val regularText = remember { SpanStyle(color = background) }
|
||||
val clickableTextStyle = remember { SpanStyle(color = primary) }
|
||||
|
||||
val annotatedTermsString =
|
||||
remember {
|
||||
buildAnnotatedString {
|
||||
withStyle(clickableTextStyle) {
|
||||
pushStringAnnotation("routeToImage", "")
|
||||
append("$url ")
|
||||
pop()
|
||||
}
|
||||
|
||||
withStyle(clickableTextStyle) {
|
||||
pushStringAnnotation("routeToImage", "")
|
||||
pop()
|
||||
}
|
||||
|
||||
withStyle(regularText) { append(" ") }
|
||||
}
|
||||
}
|
||||
|
||||
val pressIndicator =
|
||||
remember {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { runCatching { uri.openUri(url) } }
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.width(IntrinsicSize.Max),
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
Text(
|
||||
text = annotatedTermsString,
|
||||
modifier =
|
||||
pressIndicator
|
||||
.weight(1f, fill = false),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
InlineDownloadIcon(showImage)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InlineDownloadIcon(showImage: MutableState<Boolean>) =
|
||||
IconButton(
|
||||
modifier = Modifier.size(Size20dp),
|
||||
onClick = { showImage.value = true },
|
||||
) {
|
||||
DownloadForOfflineIcon(Size24dp)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WaitAndDisplay(content: @Composable (AnimatedVisibilityScope.() -> Unit)) {
|
||||
val visible = remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
delay(200)
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible.value,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayUrlWithLoadingSymbol(
|
||||
content: BaseMediaContent,
|
||||
onError: (StringResource, StringResource) -> Unit = { _, _ -> },
|
||||
) {
|
||||
val uri = LocalUriHandler.current
|
||||
|
||||
val primary = MaterialTheme.colorScheme.primary
|
||||
val background = MaterialTheme.colorScheme.onBackground
|
||||
|
||||
val regularText = remember { SpanStyle(color = background) }
|
||||
val clickableTextStyle = remember { SpanStyle(color = primary) }
|
||||
|
||||
val blossomOpener = rememberBlossomUriOpener()
|
||||
|
||||
val annotatedTermsString =
|
||||
remember {
|
||||
buildAnnotatedString {
|
||||
if (content is MediaUrlContent) {
|
||||
withStyle(clickableTextStyle) {
|
||||
pushStringAnnotation("routeToImage", "")
|
||||
append(content.url + " ")
|
||||
pop()
|
||||
}
|
||||
} else {
|
||||
withStyle(regularText) { append("Loading content...") }
|
||||
}
|
||||
|
||||
withStyle(clickableTextStyle) {
|
||||
pushStringAnnotation("routeToImage", "")
|
||||
pop()
|
||||
}
|
||||
|
||||
withStyle(regularText) { append(" ") }
|
||||
}
|
||||
}
|
||||
|
||||
val pressIndicator =
|
||||
remember {
|
||||
if (content is MediaUrlContent) {
|
||||
Modifier.clickable {
|
||||
if (content.url.startsWith("blossom:")) {
|
||||
blossomOpener.open(content.url, onError)
|
||||
} else {
|
||||
runCatching { uri.openUri(content.url) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.width(IntrinsicSize.Max),
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
Text(
|
||||
text = annotatedTermsString,
|
||||
modifier = pressIndicator.weight(1f, fill = false),
|
||||
overflow = TextOverflow.MiddleEllipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
InlineLoadingIcon()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayUrlWithLoadingSymbol(
|
||||
url: String,
|
||||
onError: (StringResource, StringResource) -> Unit = { _, _ -> },
|
||||
) {
|
||||
val uri = LocalUriHandler.current
|
||||
|
||||
val primary = MaterialTheme.colorScheme.primary
|
||||
val annotatedTermsString =
|
||||
remember {
|
||||
buildAnnotatedString {
|
||||
withStyle(SpanStyle(color = primary)) {
|
||||
pushStringAnnotation("routeToImage", "")
|
||||
append("$url ")
|
||||
pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val blossomOpener = rememberBlossomUriOpener()
|
||||
|
||||
val pressIndicator =
|
||||
remember {
|
||||
Modifier.clickable {
|
||||
if (url.startsWith("blossom:")) {
|
||||
blossomOpener.open(url, onError)
|
||||
} else {
|
||||
runCatching {
|
||||
uri.openUri(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.width(IntrinsicSize.Max),
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
Text(
|
||||
text = annotatedTermsString,
|
||||
modifier = pressIndicator.weight(1f, fill = false),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
InlineLoadingIcon()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InlineLoadingIcon() = LoadingAnimation()
|
||||
|
||||
@Composable
|
||||
fun DisplayBlurHash(
|
||||
blurhash: String?,
|
||||
description: String?,
|
||||
contentScale: ContentScale,
|
||||
modifier: Modifier,
|
||||
thumbhash: String? = null,
|
||||
) {
|
||||
val model =
|
||||
placeholderModel(thumbhash, blurhash) ?: return
|
||||
|
||||
AsyncImage(
|
||||
model = model,
|
||||
contentDescription = description,
|
||||
contentScale = contentScale,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
+2
-3
@@ -33,10 +33,9 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.google.common.math.IntMath.sqrt
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp
|
||||
import java.math.RoundingMode
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.sqrt
|
||||
|
||||
@Composable
|
||||
fun GridPreviewTemplate(count: Int) {
|
||||
@@ -93,7 +92,7 @@ fun AutoNonlazyGrid(
|
||||
content: @Composable (Int) -> Unit,
|
||||
) {
|
||||
if (itemCount > 0) {
|
||||
NonlazyGrid(sqrt(itemCount, RoundingMode.UP), itemCount, modifier, content)
|
||||
NonlazyGrid(ceil(sqrt(itemCount.toDouble())).toInt(), itemCount, modifier, content)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-5
@@ -39,7 +39,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -94,15 +93,15 @@ import com.vitorpamplona.amethyst.commons.ui.components.RenderRegular
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RenderTextParagraph
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.TagLink
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.measureSpaceWidth
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.rememberBlossomUriOpener
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.CashuCardBorders
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.HalfVertPadding
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.service.uploads.blossom.bud10.openBlossomUriAsIntent
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.invoice.ClinkOfferPreview
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.invoice.MayBeInvoicePreview
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nipB7Blossom.BlossomUri
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -444,13 +443,13 @@ fun ClickableBlossomUri(
|
||||
blossomUri: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val blossomOpener = rememberBlossomUriOpener()
|
||||
|
||||
ClickableTextPrimary(
|
||||
text = remember { BlossomUri.parse(blossomUri)?.filename() ?: blossomUri },
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.MiddleEllipsis,
|
||||
onClick = { openBlossomUriAsIntent(context, blossomUri, accountViewModel.toastManager::toast) },
|
||||
onClick = { blossomOpener.open(blossomUri) { title, message -> accountViewModel.toastManager.toast(title, message) } },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+31
-18
@@ -22,11 +22,10 @@ package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.LocalTranslationPlatform
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.MaxWidthPaddingTop5dp
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
@@ -44,19 +43,27 @@ fun TranslatableRichTextViewer(
|
||||
authorPubKey: String? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = ExpandableRichTextViewer(
|
||||
content,
|
||||
canPreview,
|
||||
quotesLeft,
|
||||
modifier,
|
||||
tags,
|
||||
backgroundColor,
|
||||
id,
|
||||
callbackUri,
|
||||
authorPubKey,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
) {
|
||||
TranslatableRichTextViewer(
|
||||
content = content,
|
||||
id = id,
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
ExpandableRichTextViewer(
|
||||
it,
|
||||
canPreview,
|
||||
quotesLeft,
|
||||
modifier,
|
||||
tags,
|
||||
backgroundColor,
|
||||
id,
|
||||
callbackUri,
|
||||
authorPubKey,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TranslatableRichTextViewer(
|
||||
@@ -65,11 +72,17 @@ fun TranslatableRichTextViewer(
|
||||
translationMessageModifier: Modifier = MaxWidthPaddingTop5dp,
|
||||
accountViewModel: AccountViewModel,
|
||||
displayText: @Composable (String) -> Unit,
|
||||
) = displayText(content)
|
||||
) = LocalTranslationPlatform.current.Translatable(content, id, translationMessageModifier, accountViewModel, displayText)
|
||||
|
||||
/** No translation service in this flavor, so the content is always its own "translation". */
|
||||
/**
|
||||
* The translation of [content] under the current language settings, or [content] unchanged when
|
||||
* no translation applies. Same machinery and cache as [TranslatableRichTextViewer] but without
|
||||
* the status bar, for callers that already render one and need a second string translated in
|
||||
* step with it — e.g. a NIP-84 highlight, which must translate the quoted passage alongside the
|
||||
* context in order to keep locating the passage inside it.
|
||||
*/
|
||||
@Composable
|
||||
fun rememberTranslation(
|
||||
content: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
): String = content
|
||||
): String = LocalTranslationPlatform.current.rememberTranslation(content, accountViewModel)
|
||||
+3
-2
@@ -80,6 +80,7 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaUrlContent
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlPdf
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
||||
import com.vitorpamplona.amethyst.commons.richtext.localJavaFile
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.SlidingCarousel
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.getActivityWindow
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.getDialogWindow
|
||||
@@ -424,7 +425,7 @@ internal suspend fun saveMediaToGallery(
|
||||
},
|
||||
)
|
||||
} else if (content is MediaPreloadedContent) {
|
||||
content.localFile?.let {
|
||||
content.localJavaFile?.let {
|
||||
MediaSaverToDisk.save(
|
||||
it,
|
||||
content.mimeType,
|
||||
@@ -566,7 +567,7 @@ private fun RenderImageOrVideo(
|
||||
Modifier.fillMaxWidth()
|
||||
}
|
||||
|
||||
content.localFile?.let {
|
||||
content.localJavaFile?.let {
|
||||
val ratio = content.dim?.aspectRatioOrNull() ?: MediaAspectRatioCache.get(it.toUri().toString())
|
||||
|
||||
val modifier =
|
||||
|
||||
+10
-246
@@ -24,14 +24,10 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -39,7 +35,6 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
@@ -59,15 +54,8 @@ import androidx.compose.ui.layout.boundsInWindow
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.compose.AsyncImagePainter
|
||||
import coil3.compose.SubcomposeAsyncImage
|
||||
import coil3.compose.SubcomposeAsyncImageContent
|
||||
@@ -105,7 +93,7 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaUrlPdf
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||
import com.vitorpamplona.amethyst.commons.richtext.isAnimatedMediaUrl
|
||||
import com.vitorpamplona.amethyst.commons.service.image.placeholderModel
|
||||
import com.vitorpamplona.amethyst.commons.richtext.localJavaFile
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.InformationDialog
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation
|
||||
@@ -116,8 +104,6 @@ import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.DownloadForOfflineIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size20dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size24dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size30Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size40dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size6dp
|
||||
@@ -128,7 +114,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.service.images.BlossomFetcher
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.VideoView
|
||||
import com.vitorpamplona.amethyst.service.uploads.blossom.bud10.openBlossomUriAsIntent
|
||||
import com.vitorpamplona.amethyst.ui.components.pdf.PdfPreviewCard
|
||||
import com.vitorpamplona.amethyst.ui.components.pdf.PdfViewerDialog
|
||||
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
||||
@@ -153,7 +138,6 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.coroutines.executeAsync
|
||||
import okio.sink
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
@@ -291,7 +275,7 @@ fun ZoomableContentView(
|
||||
|
||||
is MediaLocalImage -> {
|
||||
if (content.isAnimatedMedia()) {
|
||||
content.localFile?.let {
|
||||
content.localJavaFile?.let {
|
||||
GifVideoView(
|
||||
videoUri = it.toUri().toString(),
|
||||
contentDescription = content.description,
|
||||
@@ -319,7 +303,7 @@ fun ZoomableContentView(
|
||||
}
|
||||
|
||||
is MediaLocalVideo -> {
|
||||
content.localFile?.let {
|
||||
content.localJavaFile?.let {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().then(boundsTrackingModifier),
|
||||
contentAlignment = Alignment.Center,
|
||||
@@ -401,19 +385,19 @@ fun LocalImageView(
|
||||
)
|
||||
}
|
||||
|
||||
val ratio = remember(content) { content.dim?.aspectRatioOrNull() ?: MediaAspectRatioCache.get(content.localFile.toString()) }
|
||||
val ratio = remember(content) { content.dim?.aspectRatioOrNull() ?: MediaAspectRatioCache.get(content.localJavaFile.toString()) }
|
||||
val context = LocalContext.current
|
||||
val imageModel =
|
||||
if (fullResolution) {
|
||||
remember(content.localFile, context) {
|
||||
remember(content.localJavaFile, context) {
|
||||
ImageRequest
|
||||
.Builder(context)
|
||||
.data(content.localFile)
|
||||
.data(content.localJavaFile)
|
||||
.size(Size.ORIGINAL)
|
||||
.build()
|
||||
}
|
||||
} else {
|
||||
content.localFile
|
||||
content.localJavaFile
|
||||
}
|
||||
CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center) { imageVisible ->
|
||||
if (imageVisible) {
|
||||
@@ -467,7 +451,7 @@ fun LocalImageView(
|
||||
|
||||
SideEffect {
|
||||
val drawable = (state as AsyncImagePainter.State.Success).result.image
|
||||
MediaAspectRatioCache.add(content.localFile.toString(), drawable.width, drawable.height)
|
||||
MediaAspectRatioCache.add(content.localJavaFile.toString(), drawable.width, drawable.height)
|
||||
}
|
||||
|
||||
content.isVerified?.let {
|
||||
@@ -645,71 +629,6 @@ fun UrlImageView(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ImageUrlWithDownloadButton(
|
||||
url: String,
|
||||
showImage: MutableState<Boolean>,
|
||||
) {
|
||||
val uri = LocalUriHandler.current
|
||||
|
||||
val primary = MaterialTheme.colorScheme.primary
|
||||
val background = MaterialTheme.colorScheme.onBackground
|
||||
|
||||
val regularText = remember { SpanStyle(color = background) }
|
||||
val clickableTextStyle = remember { SpanStyle(color = primary) }
|
||||
|
||||
val annotatedTermsString =
|
||||
remember {
|
||||
buildAnnotatedString {
|
||||
withStyle(clickableTextStyle) {
|
||||
pushStringAnnotation("routeToImage", "")
|
||||
append("$url ")
|
||||
pop()
|
||||
}
|
||||
|
||||
withStyle(clickableTextStyle) {
|
||||
pushStringAnnotation("routeToImage", "")
|
||||
pop()
|
||||
}
|
||||
|
||||
withStyle(regularText) { append(" ") }
|
||||
}
|
||||
}
|
||||
|
||||
val pressIndicator =
|
||||
remember {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { runCatching { uri.openUri(url) } }
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.width(IntrinsicSize.Max),
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
Text(
|
||||
text = annotatedTermsString,
|
||||
modifier =
|
||||
pressIndicator
|
||||
.weight(1f, fill = false),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
InlineDownloadIcon(showImage)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InlineDownloadIcon(showImage: MutableState<Boolean>) =
|
||||
IconButton(
|
||||
modifier = Modifier.size(Size20dp),
|
||||
onClick = { showImage.value = true },
|
||||
) {
|
||||
DownloadForOfflineIcon(Size24dp)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ShowHashAnimated(
|
||||
content: MediaUrlImage,
|
||||
@@ -761,161 +680,6 @@ fun BaseMediaContent.isAnimatedMedia(): Boolean =
|
||||
false
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WaitAndDisplay(content: @Composable (AnimatedVisibilityScope.() -> Unit)) {
|
||||
val visible = remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
delay(200)
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible.value,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayUrlWithLoadingSymbol(
|
||||
content: BaseMediaContent,
|
||||
onError: (StringResource, StringResource) -> Unit = { _, _ -> },
|
||||
) {
|
||||
val uri = LocalUriHandler.current
|
||||
|
||||
val primary = MaterialTheme.colorScheme.primary
|
||||
val background = MaterialTheme.colorScheme.onBackground
|
||||
|
||||
val regularText = remember { SpanStyle(color = background) }
|
||||
val clickableTextStyle = remember { SpanStyle(color = primary) }
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
val annotatedTermsString =
|
||||
remember {
|
||||
buildAnnotatedString {
|
||||
if (content is MediaUrlContent) {
|
||||
withStyle(clickableTextStyle) {
|
||||
pushStringAnnotation("routeToImage", "")
|
||||
append(content.url + " ")
|
||||
pop()
|
||||
}
|
||||
} else {
|
||||
withStyle(regularText) { append("Loading content...") }
|
||||
}
|
||||
|
||||
withStyle(clickableTextStyle) {
|
||||
pushStringAnnotation("routeToImage", "")
|
||||
pop()
|
||||
}
|
||||
|
||||
withStyle(regularText) { append(" ") }
|
||||
}
|
||||
}
|
||||
|
||||
val pressIndicator =
|
||||
remember {
|
||||
if (content is MediaUrlContent) {
|
||||
Modifier.clickable {
|
||||
if (content.url.startsWith("blossom:")) {
|
||||
openBlossomUriAsIntent(context, content.url, onError)
|
||||
} else {
|
||||
runCatching { uri.openUri(content.url) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.width(IntrinsicSize.Max),
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
Text(
|
||||
text = annotatedTermsString,
|
||||
modifier = pressIndicator.weight(1f, fill = false),
|
||||
overflow = TextOverflow.MiddleEllipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
InlineLoadingIcon()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayUrlWithLoadingSymbol(
|
||||
url: String,
|
||||
onError: (StringResource, StringResource) -> Unit = { _, _ -> },
|
||||
) {
|
||||
val uri = LocalUriHandler.current
|
||||
|
||||
val primary = MaterialTheme.colorScheme.primary
|
||||
val annotatedTermsString =
|
||||
remember {
|
||||
buildAnnotatedString {
|
||||
withStyle(SpanStyle(color = primary)) {
|
||||
pushStringAnnotation("routeToImage", "")
|
||||
append("$url ")
|
||||
pop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
val pressIndicator =
|
||||
remember {
|
||||
Modifier.clickable {
|
||||
if (url.startsWith("blossom:")) {
|
||||
openBlossomUriAsIntent(context, url, onError)
|
||||
} else {
|
||||
runCatching {
|
||||
uri.openUri(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.width(IntrinsicSize.Max),
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
) {
|
||||
Text(
|
||||
text = annotatedTermsString,
|
||||
modifier = pressIndicator.weight(1f, fill = false),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
InlineLoadingIcon()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InlineLoadingIcon() = LoadingAnimation()
|
||||
|
||||
@Composable
|
||||
fun DisplayBlurHash(
|
||||
blurhash: String?,
|
||||
description: String?,
|
||||
contentScale: ContentScale,
|
||||
modifier: Modifier,
|
||||
thumbhash: String? = null,
|
||||
) {
|
||||
val model =
|
||||
placeholderModel(thumbhash, blurhash) ?: return
|
||||
|
||||
AsyncImage(
|
||||
model = model,
|
||||
contentDescription = description,
|
||||
contentScale = contentScale,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ShareMediaAction(
|
||||
accountViewModel: AccountViewModel,
|
||||
@@ -939,7 +703,7 @@ fun ShareMediaAction(
|
||||
} else if (content is MediaPreloadedContent) {
|
||||
ShareMediaAction(
|
||||
popupExpanded = popupExpanded,
|
||||
videoUri = content.localFile?.toUri().toString(),
|
||||
videoUri = content.localJavaFile?.toUri().toString(),
|
||||
postNostrUri = content.uri,
|
||||
blurhash = content.blurhash,
|
||||
dim = content.dim,
|
||||
@@ -1083,7 +847,7 @@ fun ShareMediaAction(
|
||||
}
|
||||
|
||||
is MediaLocalVideo -> {
|
||||
content.localFile?.let { localFile ->
|
||||
content.localJavaFile?.let { localFile ->
|
||||
M3ActionRow(icon = MaterialSymbols.Share, text = stringRes(Res.string.share_video)) {
|
||||
accountViewModel.viewModelScope.launch { shareLocalVideoFile(context, localFile, mimeType) }
|
||||
onDismiss()
|
||||
|
||||
+1
-1
@@ -50,8 +50,8 @@ import com.vitorpamplona.amethyst.commons.ui.theme.MarkdownTextStyle
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.markdownStyle
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.uriToRoute
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongFormContentEvent
|
||||
|
||||
+4
-4
@@ -41,19 +41,20 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.error_dialog_talk_to_user
|
||||
import com.vitorpamplona.amethyst.commons.resources.error_dialog_talk_to_user_name
|
||||
import com.vitorpamplona.amethyst.commons.resources.error_dialog_zap_error
|
||||
import com.vitorpamplona.amethyst.commons.resources.ic_dm
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.toasts.multiline.MultiErrorToastMsg
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.toasts.multiline.UserBasedErrorMessage
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeToMessage
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.UserPicture
|
||||
import com.vitorpamplona.amethyst.commons.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier
|
||||
@@ -64,8 +65,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -146,7 +146,7 @@ fun ErrorRow(
|
||||
} ?: stringRes(Res.string.error_dialog_talk_to_user)
|
||||
|
||||
Icon(
|
||||
painter = painterRes(R.drawable.ic_dm, 2),
|
||||
painter = painterRes(Res.drawable.ic_dm, 2),
|
||||
contentDescription = descriptor,
|
||||
modifier = Size20Modifier,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size16dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
+5
-3
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.navigation.drawer
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -128,6 +129,7 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.PermanentDrawerWidth
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
@@ -152,11 +154,11 @@ import com.vitorpamplona.amethyst.commons.ui.theme.drawerSpacing
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.profileContentHeaderModifier
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.isDebug
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCatalog
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarItemDef
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -297,8 +299,8 @@ fun ProfileContentTemplate(
|
||||
modifier = bannerModifier,
|
||||
)
|
||||
} else {
|
||||
AsyncImage(
|
||||
model = R.drawable.profile_banner,
|
||||
Image(
|
||||
painter = painterRes(Res.drawable.profile_banner, 3),
|
||||
contentDescription = stringRes(Res.string.profile_banner),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = bannerModifier,
|
||||
|
||||
@@ -54,7 +54,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ButtonPadding
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
import kotlinx.collections.immutable.persistentSetOf
|
||||
|
||||
|
||||
@@ -39,17 +39,17 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.error_dialog_button_ok
|
||||
import com.vitorpamplona.amethyst.commons.resources.error_dialog_talk_to_user
|
||||
import com.vitorpamplona.amethyst.commons.resources.ic_dm
|
||||
import com.vitorpamplona.amethyst.commons.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size16dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
@@ -84,7 +84,7 @@ fun ErrorMessageDialog(
|
||||
onClickStartMessage?.let {
|
||||
TextButton(onClick = onClickStartMessage) {
|
||||
Icon(
|
||||
painter = painterRes(R.drawable.ic_dm, 3),
|
||||
painter = painterRes(Res.drawable.ic_dm, 3),
|
||||
contentDescription = null,
|
||||
)
|
||||
Spacer(StdHorzSpacer)
|
||||
|
||||
@@ -39,15 +39,16 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.notifications.MessageSetCard
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.ic_dm
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdStartPadding
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@@ -122,7 +123,7 @@ fun MessageSetCompose(
|
||||
fun MessageIconBox() {
|
||||
Box(Modifier.width(55.dp).padding(top = 5.dp, end = 5.dp)) {
|
||||
Icon(
|
||||
painter = painterRes(R.drawable.ic_dm, 4),
|
||||
painter = painterRes(Res.drawable.ic_dm, 4),
|
||||
null,
|
||||
modifier = Modifier.size(16.dp).align(Alignment.TopEnd),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
|
||||
@@ -96,6 +96,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.WatchAuthorWithBlank
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.WatchUserFollows
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ZappedIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.InLineIconRenderer
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.LocalRichTextPlatform
|
||||
import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.BitcoinOrange
|
||||
@@ -116,13 +117,13 @@ import com.vitorpamplona.amethyst.commons.ui.theme.overPictureBackground
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.profile35dpModifier
|
||||
import com.vitorpamplona.amethyst.commons.util.showAmount
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.components.CoreSecretMessage
|
||||
import com.vitorpamplona.amethyst.ui.components.ExpandableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
|
||||
import com.vitorpamplona.quartz.nip61Nutzaps.nutzap.NutzapEvent
|
||||
import com.vitorpamplona.quartz.nip61Nutzaps.nutzap.claimedSatsTotal
|
||||
import com.vitorpamplona.quartz.utils.BigDecimal
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@@ -393,7 +394,7 @@ fun DisplaySecretEmojiAsReaction(
|
||||
) {
|
||||
Surface(Modifier.padding(10.dp)) {
|
||||
val color = remember { mutableStateOf(Color.Transparent) }
|
||||
CoreSecretMessage(localSecretContent, null, 3, color, accountViewModel, nav)
|
||||
LocalRichTextPlatform.current.SecretMessage(localSecretContent, null, 3, color, accountViewModel, nav)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -456,7 +457,7 @@ fun RenderNutzapGallery(
|
||||
ZapAmountCommentNotification(
|
||||
user = note.author,
|
||||
comment = event?.content?.ifBlank { null },
|
||||
amount = showAmount(java.math.BigDecimal(sats)),
|
||||
amount = showAmount(BigDecimal(sats)),
|
||||
zapNote = note,
|
||||
)
|
||||
}.toImmutableList()
|
||||
|
||||
+3
-3
@@ -41,7 +41,6 @@ import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
|
||||
import com.vitorpamplona.amethyst.commons.hashtags.Tunestr
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
@@ -59,6 +58,7 @@ import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip05_checking
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip05_failed
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip05_verified
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip_05
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.ClickableTextPrimary
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.LoadNote
|
||||
@@ -67,6 +67,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadUser
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.WatchAuthor
|
||||
import com.vitorpamplona.amethyst.commons.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Font14SP
|
||||
@@ -79,7 +80,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.nip05
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.firstTaggedAddress
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
@@ -484,7 +484,7 @@ fun RenderNIP05VerifiedSymbol(
|
||||
|
||||
is Nip05VerifState.Verified -> {
|
||||
Icon(
|
||||
painter = painterRes(R.drawable.nip_05, compositionSizeReference),
|
||||
painter = painterRes(Res.drawable.nip_05, compositionSizeReference),
|
||||
contentDescription = stringRes(id = Res.string.nip05_verified),
|
||||
modifier = modifier,
|
||||
tint = MaterialTheme.colorScheme.nip05,
|
||||
|
||||
@@ -95,6 +95,7 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.NoteComposeLayout
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeEditDraftTo
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.DateTimeStyle
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.HeaderPill
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadPublicChatChannel
|
||||
@@ -109,6 +110,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.elements.StaleRelayHint
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.elements.TimeAgo
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.elements.TimeAgoStyle
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.formatDateTime
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.timeAheadNoDot
|
||||
import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings
|
||||
import com.vitorpamplona.amethyst.commons.ui.state.produceCachedStateAsync
|
||||
@@ -465,8 +467,6 @@ import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
@Composable
|
||||
fun NoteCompose(
|
||||
@@ -2126,7 +2126,7 @@ fun DisplayExpiration(
|
||||
accountViewModel.toastManager.toast(
|
||||
Res.string.expiration_date_label,
|
||||
Res.string.expiration_info_description,
|
||||
SimpleDateFormat.getDateTimeInstance().format(Date(expirationDate * 1000)),
|
||||
formatDateTime(expirationDate * 1000, DateTimeStyle.MEDIUM, DateTimeStyle.MEDIUM),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -20,8 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -55,14 +53,11 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Popup
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
@@ -102,6 +97,8 @@ import com.vitorpamplona.amethyst.commons.resources.quick_action_unfollow
|
||||
import com.vitorpamplona.amethyst.commons.resources.quick_action_unmute_thread
|
||||
import com.vitorpamplona.amethyst.commons.resources.report_dialog_block_hide_user_btn
|
||||
import com.vitorpamplona.amethyst.commons.resources.report_dialog_blocking_a_user
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.rememberShortNotice
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.rememberTextSharer
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
@@ -114,7 +111,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.isLight
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.secondaryButtonBackground
|
||||
import com.vitorpamplona.amethyst.commons.util.njumpLink
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.report.ReportNoteDialog
|
||||
import com.vitorpamplona.quartz.experimental.bounties.bountyBaseReward
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
@@ -122,17 +118,38 @@ import com.vitorpamplona.quartz.nip51Lists.followSet.FollowSetEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import kotlin.math.abs
|
||||
|
||||
/** [color] with its HSL lightness raised by [amount], opaque, as `ColorUtils` computed it. */
|
||||
private fun lightenColor(
|
||||
color: Color,
|
||||
amount: Float,
|
||||
): Color {
|
||||
var argb = color.toArgb()
|
||||
val hslOut = floatArrayOf(0f, 0f, 0f)
|
||||
ColorUtils.colorToHSL(argb, hslOut)
|
||||
hslOut[2] += amount
|
||||
argb = ColorUtils.HSLToColor(hslOut)
|
||||
return Color(argb)
|
||||
val r = color.red
|
||||
val g = color.green
|
||||
val b = color.blue
|
||||
val max = maxOf(r, g, b)
|
||||
val min = minOf(r, g, b)
|
||||
val delta = max - min
|
||||
val lightness = (max + min) / 2f
|
||||
|
||||
var hue = 0f
|
||||
var saturation = 0f
|
||||
if (delta != 0f) {
|
||||
hue =
|
||||
when (max) {
|
||||
r -> ((g - b) / delta).mod(6f)
|
||||
g -> (b - r) / delta + 2f
|
||||
else -> (r - g) / delta + 4f
|
||||
} * 60f
|
||||
saturation = delta / (1f - abs(2f * lightness - 1f))
|
||||
}
|
||||
|
||||
return Color.hsl(
|
||||
hue = hue.mod(360f),
|
||||
saturation = saturation.coerceIn(0f, 1f),
|
||||
lightness = (lightness + amount).coerceIn(0f, 1f),
|
||||
)
|
||||
}
|
||||
|
||||
val externalLinkForUser = { user: User ->
|
||||
@@ -289,19 +306,15 @@ fun CardBody(
|
||||
) {
|
||||
val quickActionShareBrowserLinkStr = stringRes(Res.string.quick_action_share_browser_link)
|
||||
val quickActionShareStr = stringRes(Res.string.quick_action_share)
|
||||
val context = LocalContext.current
|
||||
val notice = rememberShortNotice()
|
||||
val sharer = rememberTextSharer()
|
||||
val primaryLight = lightenColor(MaterialTheme.colorScheme.primary, 0.1f)
|
||||
val clipboardManager = LocalClipboard.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val showToast = { stringRes: StringResource ->
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
loadStringRes(stringRes),
|
||||
Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
notice.show(loadStringRes(stringRes))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,26 +505,7 @@ fun CardBody(
|
||||
icon = MaterialSymbols.Share,
|
||||
label = stringRes(Res.string.quick_action_share),
|
||||
) {
|
||||
val sendIntent =
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
type = "text/plain"
|
||||
putExtra(
|
||||
Intent.EXTRA_TEXT,
|
||||
externalLinkForNote(note),
|
||||
)
|
||||
putExtra(
|
||||
Intent.EXTRA_TITLE,
|
||||
quickActionShareBrowserLinkStr,
|
||||
)
|
||||
}
|
||||
|
||||
val shareIntent =
|
||||
Intent.createChooser(
|
||||
sendIntent,
|
||||
quickActionShareStr,
|
||||
)
|
||||
context.startActivity(shareIntent)
|
||||
sharer.share(externalLinkForNote(note), quickActionShareBrowserLinkStr, quickActionShareStr)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
@@ -675,35 +669,6 @@ fun QuickActionAlertDialog(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun QuickActionAlertDialog(
|
||||
title: String,
|
||||
textContent: String,
|
||||
buttonIconResource: Int,
|
||||
buttonIconReference: Int,
|
||||
buttonText: String,
|
||||
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||
onClickDoOnce: () -> Unit,
|
||||
onClickDontShowAgain: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
QuickActionAlertDialog(
|
||||
title = title,
|
||||
textContent = textContent,
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterRes(buttonIconResource, buttonIconReference),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
buttonText = buttonText,
|
||||
buttonColors = buttonColors,
|
||||
onClickDoOnce = onClickDoOnce,
|
||||
onClickDontShowAgain = onClickDontShowAgain,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun QuickActionAlertDialog(
|
||||
title: String,
|
||||
@@ -774,33 +739,6 @@ fun QuickActionAlertDialogOneButton(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun QuickActionAlertDialogOneButton(
|
||||
title: String,
|
||||
textContent: String,
|
||||
buttonIconResource: Int,
|
||||
buttonIconReference: Int,
|
||||
buttonText: String,
|
||||
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||
onClickDoOnce: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
QuickActionAlertDialogOneButton(
|
||||
title = title,
|
||||
textContent = textContent,
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterRes(buttonIconResource, buttonIconReference),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
buttonText = buttonText,
|
||||
buttonColors = buttonColors,
|
||||
onClickDoOnce = onClickDoOnce,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun QuickActionAlertDialogOneButton(
|
||||
title: String,
|
||||
|
||||
@@ -120,8 +120,8 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.util.showAmount
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.keyBackup.getFragmentActivity
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -60,7 +60,6 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
@@ -110,9 +109,9 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ripple24dp
|
||||
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.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockVitorAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockVitorAccountViewModel
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -544,7 +543,6 @@ fun ZapVote(
|
||||
|
||||
var showErrorMessageDialog by remember { mutableStateOf<StringToastMsg?>(null) }
|
||||
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
nonClickablePrepend()
|
||||
@@ -603,9 +601,9 @@ fun ZapVote(
|
||||
onPayViaIntent = {
|
||||
if (it.size == 1) {
|
||||
val payable = it.first()
|
||||
payViaIntent(payable.invoice, context, noWalletFoundStr, { }) { error ->
|
||||
if (!accountViewModel.host.openLightningWallet(payable.invoice)) {
|
||||
zappingProgress = 0f
|
||||
showErrorMessageDialog = StringToastMsg(errorDialogZapErrorStr, error)
|
||||
showErrorMessageDialog = StringToastMsg(errorDialogZapErrorStr, noWalletFoundStr)
|
||||
}
|
||||
} else {
|
||||
val uid = Uuid.random().toString()
|
||||
@@ -624,7 +622,6 @@ fun ZapVote(
|
||||
),
|
||||
) {
|
||||
if (wantsToZap) {
|
||||
val context = LocalContext.current
|
||||
FilteredZapAmountChoicePopup(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
@@ -644,9 +641,9 @@ fun ZapVote(
|
||||
onPayViaIntent = {
|
||||
if (it.size == 1) {
|
||||
val payable = it.first()
|
||||
payViaIntent(payable.invoice, context, noWalletFoundStr, { }) { error ->
|
||||
if (!accountViewModel.host.openLightningWallet(payable.invoice)) {
|
||||
zappingProgress = 0f
|
||||
showErrorMessageDialog = StringToastMsg(errorDialogZapErrorStr, error)
|
||||
showErrorMessageDialog = StringToastMsg(errorDialogZapErrorStr, noWalletFoundStr)
|
||||
}
|
||||
} else {
|
||||
val uid = Uuid.random().toString()
|
||||
|
||||
@@ -32,17 +32,21 @@ import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.ZapRequestEvent
|
||||
import com.vitorpamplona.quartz.utils.BigDecimal
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.utils.plus
|
||||
import com.vitorpamplona.quartz.utils.toDoubleValue
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
private val ZERO = BigDecimal(0)
|
||||
|
||||
@Stable
|
||||
data class PollOption(
|
||||
val option: Int,
|
||||
val descriptor: String,
|
||||
var zappedValue: MutableState<BigDecimal> = mutableStateOf(BigDecimal.ZERO),
|
||||
var zappedValue: MutableState<BigDecimal> = mutableStateOf(ZERO),
|
||||
var tally: MutableState<Float> = mutableFloatStateOf(0f),
|
||||
var consensusThreadhold: MutableState<Boolean> = mutableStateOf(false),
|
||||
var zappedByLoggedIn: MutableState<Boolean> = mutableStateOf(false),
|
||||
@@ -57,13 +61,11 @@ class PollNoteViewModel : ViewModel() {
|
||||
private var pollOptions: Map<Int, String>? = null
|
||||
private var valueMaximum: Long? = null
|
||||
private var valueMinimum: Long? = null
|
||||
private var valueMaximumBD: BigDecimal? = null
|
||||
private var valueMinimumBD: BigDecimal? = null
|
||||
|
||||
private var closedAt: Long? = null
|
||||
private var consensusThreshold: BigDecimal? = null
|
||||
private var consensusThreshold: Double? = null
|
||||
|
||||
private var totalZapped: BigDecimal = BigDecimal.ZERO
|
||||
private var totalZapped: BigDecimal = ZERO
|
||||
private var wasZappedByLoggedInAccount: Boolean = false
|
||||
|
||||
var canZap = mutableStateOf(false)
|
||||
@@ -80,12 +82,10 @@ class PollNoteViewModel : ViewModel() {
|
||||
pollOptions = pollEvent?.pollOptions()
|
||||
valueMaximum = pollEvent?.maxAmount()
|
||||
valueMinimum = pollEvent?.minAmount()
|
||||
valueMinimumBD = valueMinimum?.let { BigDecimal(it) }
|
||||
valueMaximumBD = valueMaximum?.let { BigDecimal(it) }
|
||||
consensusThreshold = pollEvent?.consensusThreshold()?.toBigDecimal()
|
||||
consensusThreshold = pollEvent?.consensusThreshold()
|
||||
closedAt = pollEvent?.closedAt()
|
||||
|
||||
totalZapped = BigDecimal.ZERO
|
||||
totalZapped = ZERO
|
||||
wasZappedByLoggedInAccount = false
|
||||
|
||||
canZap.value = checkIfCanZap()
|
||||
@@ -108,16 +108,18 @@ class PollNoteViewModel : ViewModel() {
|
||||
|
||||
tallies.forEach {
|
||||
val zappedValue = zappedPollOptionAmount(it.option)
|
||||
val total = totalZapped.toDoubleValue()
|
||||
// The share, rounded to two decimals.
|
||||
val tallyValue =
|
||||
if (totalZapped > BigDecimal.ZERO) {
|
||||
zappedValue.divide(totalZapped, 2, RoundingMode.HALF_UP)
|
||||
if (total > 0) {
|
||||
(zappedValue.toDoubleValue() / total * 100).roundToLong() / 100.0
|
||||
} else {
|
||||
BigDecimal.ZERO
|
||||
0.0
|
||||
}
|
||||
|
||||
it.zappedValue.value = zappedValue
|
||||
it.tally.value = tallyValue.toFloat()
|
||||
it.consensusThreadhold.value = consensusThreshold != null && tallyValue >= consensusThreshold!!
|
||||
it.consensusThreadhold.value = consensusThreshold?.let { threshold -> tallyValue >= threshold } == true
|
||||
it.zappedByLoggedIn.value = account.userProfile().let { it1 -> cachedIsPollOptionZappedBy(it.option, it1) }
|
||||
}
|
||||
}
|
||||
@@ -157,36 +159,16 @@ class PollNoteViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun isValidInputVoteAmount(amount: BigDecimal?): Boolean {
|
||||
when {
|
||||
amount == null -> {
|
||||
return false
|
||||
}
|
||||
|
||||
valueMinimum == null && valueMaximum == null -> {
|
||||
if (amount > BigDecimal.ZERO) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
valueMinimum == null -> {
|
||||
if (amount > BigDecimal.ZERO && amount <= valueMaximumBD!!) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
valueMaximum == null -> {
|
||||
if (amount >= valueMinimumBD!!) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
if ((valueMinimumBD!! <= amount) && (amount <= valueMaximumBD!!)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if (amount == null) return false
|
||||
val sats = amount.toDoubleValue()
|
||||
val min = valueMinimum
|
||||
val max = valueMaximum
|
||||
return when {
|
||||
min == null && max == null -> sats > 0
|
||||
min == null -> sats > 0 && sats <= max!!
|
||||
max == null -> sats >= min
|
||||
else -> min <= sats && sats <= max
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun isValidInputVoteAmount(amount: Long?): Boolean {
|
||||
@@ -243,32 +225,32 @@ class PollNoteViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
private fun zappedPollOptionAmount(option: Int): BigDecimal =
|
||||
pollNote?.zaps?.values?.sumOf {
|
||||
pollNote?.zaps?.values?.fold(ZERO) { acc, it ->
|
||||
val event = it?.event as? ZapReceiptEvent
|
||||
val zapAmount = event?.amount ?: BigDecimal.ZERO
|
||||
val zapAmount = event?.amount ?: ZERO
|
||||
val isValidAmount = isValidInputVoteAmount(event?.amount)
|
||||
|
||||
if (isValidAmount && event?.zappedPollOption() == option) {
|
||||
zapAmount
|
||||
acc + zapAmount
|
||||
} else {
|
||||
BigDecimal.ZERO
|
||||
acc
|
||||
}
|
||||
}
|
||||
?: BigDecimal.ZERO
|
||||
?: ZERO
|
||||
|
||||
private fun totalZapped(): BigDecimal =
|
||||
pollNote?.zaps?.values?.sumOf {
|
||||
pollNote?.zaps?.values?.fold(ZERO) { acc, it ->
|
||||
val zapEvent = (it?.event as? ZapReceiptEvent)
|
||||
val zapAmount = zapEvent?.amount ?: BigDecimal.ZERO
|
||||
val zapAmount = zapEvent?.amount ?: ZERO
|
||||
val isValidAmount = isValidInputVoteAmount(zapEvent?.amount)
|
||||
|
||||
if (isValidAmount && zapEvent?.zappedPollOption() != null) {
|
||||
zapAmount
|
||||
acc + zapAmount
|
||||
} else {
|
||||
BigDecimal.ZERO
|
||||
acc
|
||||
}
|
||||
}
|
||||
?: BigDecimal.ZERO
|
||||
?: ZERO
|
||||
|
||||
fun createZapOptionsThatMatchThePollingParameters(zapPaymentChoices: List<Long>): List<Long> {
|
||||
val options =
|
||||
|
||||
+1
-1
@@ -247,7 +247,7 @@ private fun MyLoadUrlPreviewDirect(
|
||||
)
|
||||
|
||||
Text(
|
||||
text = state.previewInfo.verifiedUrl?.host ?: state.previewInfo.title,
|
||||
text = state.previewInfo.verifiedHost ?: state.previewInfo.title,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.dm.AddDMRelayListDialog
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.DmRelayListEvent
|
||||
|
||||
+2
-3
@@ -28,7 +28,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.observeUserBanner
|
||||
@@ -37,6 +36,7 @@ import com.vitorpamplona.amethyst.commons.resources.preview_card_image_for
|
||||
import com.vitorpamplona.amethyst.commons.resources.profile_banner
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.BaseUserPicture
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.WatchAuthor
|
||||
import com.vitorpamplona.amethyst.commons.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.SimpleHeaderImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size16dp
|
||||
@@ -44,7 +44,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size55dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.authorNotePictureForImageHeader
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
|
||||
@Composable
|
||||
fun DefaultImageHeader(
|
||||
@@ -145,7 +144,7 @@ fun DefaultProfileBanner(
|
||||
sizeReference: Int,
|
||||
) {
|
||||
Image(
|
||||
painter = painterRes(R.drawable.profile_banner, sizeReference),
|
||||
painter = painterRes(Res.drawable.profile_banner, sizeReference),
|
||||
contentDescription = stringRes(Res.string.profile_banner),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = modifier,
|
||||
|
||||
@@ -28,13 +28,13 @@ import com.vitorpamplona.amethyst.commons.resources.ots_info_description
|
||||
import com.vitorpamplona.amethyst.commons.resources.ots_info_pending_description
|
||||
import com.vitorpamplona.amethyst.commons.resources.ots_info_title
|
||||
import com.vitorpamplona.amethyst.commons.resources.timestamp_pending_short
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.DateTimeStyle
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.HeaderPill
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadOts
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.formatDateTime
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.timeAgoNoDot
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* Compact pill showing the note's NIP-03 OpenTimestamps proof: how long ago
|
||||
@@ -60,7 +60,7 @@ fun DisplayOts(
|
||||
accountViewModel.toastManager.toast(
|
||||
Res.string.ots_info_title,
|
||||
Res.string.ots_info_description,
|
||||
SimpleDateFormat.getDateTimeInstance().format(Date(unixtimestamp * 1000)),
|
||||
formatDateTime(unixtimestamp * 1000, DateTimeStyle.MEDIUM, DateTimeStyle.MEDIUM),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -68,9 +68,10 @@ import com.vitorpamplona.amethyst.commons.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.util.showAmount
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.quartz.utils.BigDecimal
|
||||
import com.vitorpamplona.quartz.utils.parseBigDecimalOrNull
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Stable data class Reward(
|
||||
val amount: BigDecimal,
|
||||
@@ -150,7 +151,7 @@ class AddBountyAmountViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun sendPost() {
|
||||
val newValue = nextAmount.text.trim().toBigDecimalOrNull()
|
||||
val newValue = parseBigDecimalOrNull(nextAmount.text.trim())
|
||||
|
||||
if (newValue != null) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
|
||||
+2
-2
@@ -74,11 +74,11 @@ import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.LightRedColor
|
||||
import com.vitorpamplona.amethyst.commons.util.toPrettyJson
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.QuickActionAlertDialogOneButton
|
||||
import com.vitorpamplona.amethyst.ui.note.copyNoteTextAction
|
||||
import com.vitorpamplona.quartz.experimental.music.track.MusicTrackEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongFormContentEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
@@ -210,7 +210,7 @@ fun noteActionSections(
|
||||
val event = note.event
|
||||
if (event != null) {
|
||||
scope.launch {
|
||||
val json = withContext(Dispatchers.Default) { JacksonMapper.toJsonPretty(event) }
|
||||
val json = withContext(Dispatchers.Default) { event.toPrettyJson() }
|
||||
clipboardManager.setText(json)
|
||||
handlers.onDismiss()
|
||||
}
|
||||
|
||||
+1
-1
@@ -36,11 +36,11 @@ import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.service.location.CachedReversedGeoLocations
|
||||
import com.vitorpamplona.amethyst.ui.note.FirstUserInfoRow
|
||||
import com.vitorpamplona.amethyst.ui.note.types.EditState
|
||||
import com.vitorpamplona.amethyst.ui.note.types.EmptyState
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.elements
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
@@ -34,6 +32,7 @@ import com.vitorpamplona.amethyst.commons.resources.share_as_image
|
||||
import com.vitorpamplona.amethyst.commons.resources.share_as_image_url
|
||||
import com.vitorpamplona.amethyst.commons.resources.share_as_qr
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionRow
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.rememberTextSharer
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.note.externalLinkForNote
|
||||
@@ -59,21 +58,13 @@ fun ShareActionRows(
|
||||
) {
|
||||
val quickActionShareBrowserLinkStr = stringRes(Res.string.quick_action_share_browser_link)
|
||||
val quickActionShareStr = stringRes(Res.string.quick_action_share)
|
||||
val actContext = LocalContext.current
|
||||
val sharer = rememberTextSharer()
|
||||
// AddressableNotes are shared by their replaceable address; everything else
|
||||
// by event id. The two image routes resolve the note from this same id.
|
||||
val shareId = if (note is AddressableNote) note.address.toValue() else note.idHex
|
||||
|
||||
M3ActionRow(icon = MaterialSymbols.Share, text = stringRes(Res.string.quick_action_share)) {
|
||||
val sendIntent =
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TEXT, externalLinkForNote(note))
|
||||
putExtra(Intent.EXTRA_TITLE, quickActionShareBrowserLinkStr)
|
||||
}
|
||||
val shareIntent = Intent.createChooser(sendIntent, quickActionShareStr)
|
||||
actContext.startActivity(shareIntent)
|
||||
sharer.share(externalLinkForNote(note), quickActionShareBrowserLinkStr, quickActionShareStr)
|
||||
onDismiss()
|
||||
}
|
||||
M3ActionRow(icon = MaterialSymbols.Image, text = stringRes(Res.string.share_as_image)) {
|
||||
|
||||
+562
@@ -0,0 +1,562 @@
|
||||
/*
|
||||
* 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.platform
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
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.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.types.EditState
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import com.vitorpamplona.quartz.podcasts.PodcastAudio
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.VideoView as AppVideoView
|
||||
import com.vitorpamplona.amethyst.ui.actions.EditPostView as AppEditPostView
|
||||
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.note.LikeReaction as AppLikeReaction
|
||||
import com.vitorpamplona.amethyst.ui.note.ReactionsRow as AppReactionsRow
|
||||
import com.vitorpamplona.amethyst.ui.note.ZapReaction as AppZapReaction
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName as AppLoadCityName
|
||||
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.RenderAudioTrack as AppRenderAudioTrack
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderAudioWithWaveform as AppRenderAudioWithWaveform
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderChessGame as AppRenderChessGame
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderGitIssueEvent as AppRenderGitIssueEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderGitPatchEvent as AppRenderGitPatchEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderGitPullRequestEvent as AppRenderGitPullRequestEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderGitPullRequestUpdateEvent as AppRenderGitPullRequestUpdateEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderGitRepositoryEvent as AppRenderGitRepositoryEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveChessChallenge as AppRenderLiveChessChallenge
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderLiveChessGameEnd as AppRenderLiveChessGameEnd
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingRoomEvent as AppRenderMeetingRoomEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingRoomPresence as AppRenderMeetingRoomPresence
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderMeetingSpaceEvent as AppRenderMeetingSpaceEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderMusicTrack as AppRenderMusicTrack
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderNamedNappletEvent as AppRenderNamedNappletEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderNamedSiteEvent as AppRenderNamedSiteEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderRootNappletEvent as AppRenderRootNappletEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderRootSiteEvent as AppRenderRootSiteEvent
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderVoiceTrack as AppRenderVoiceTrack
|
||||
|
||||
/** Android's [NotePlatform]: the app's own media, map, audio, chess, git, napplet and zap composables. */
|
||||
object AndroidNotePlatform : NotePlatform {
|
||||
@Composable
|
||||
override fun ZoomableContentView(
|
||||
content: BaseMediaContent,
|
||||
images: ImmutableList<BaseMediaContent>,
|
||||
roundedCorner: Boolean,
|
||||
contentScale: ContentScale,
|
||||
accountViewModel: AccountViewModel,
|
||||
) = AppZoomableContentView(
|
||||
content = content,
|
||||
images = images,
|
||||
roundedCorner = roundedCorner,
|
||||
contentScale = contentScale,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun ZoomableImageDialog(
|
||||
imageUrl: BaseMediaContent,
|
||||
allImages: ImmutableList<BaseMediaContent>,
|
||||
sourceBounds: Rect?,
|
||||
onDismiss: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
) = AppZoomableImageDialog(
|
||||
imageUrl = imageUrl,
|
||||
allImages = allImages,
|
||||
sourceBounds = sourceBounds,
|
||||
onDismiss = onDismiss,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun GifVideoView(
|
||||
videoUri: String,
|
||||
contentDescription: String?,
|
||||
dimensions: DimensionTag?,
|
||||
blurhash: String?,
|
||||
roundedCorner: Boolean,
|
||||
contentScale: ContentScale,
|
||||
onDialog: (() -> Unit)?,
|
||||
accountViewModel: AccountViewModel,
|
||||
thumbhash: String?,
|
||||
) = AppGifVideoView(
|
||||
videoUri = videoUri,
|
||||
contentDescription = contentDescription,
|
||||
dimensions = dimensions,
|
||||
blurhash = blurhash,
|
||||
roundedCorner = roundedCorner,
|
||||
contentScale = contentScale,
|
||||
onDialog = onDialog,
|
||||
accountViewModel = accountViewModel,
|
||||
thumbhash = thumbhash,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun VideoView(
|
||||
videoUri: String,
|
||||
mimeType: String?,
|
||||
title: String?,
|
||||
artworkUri: String?,
|
||||
authorName: String?,
|
||||
roundedCorner: Boolean,
|
||||
contentScale: ContentScale,
|
||||
nostrUriCallback: String?,
|
||||
isLiveStream: Boolean,
|
||||
accountViewModel: AccountViewModel,
|
||||
) = AppVideoView(
|
||||
videoUri = videoUri,
|
||||
mimeType = mimeType,
|
||||
title = title,
|
||||
artworkUri = artworkUri,
|
||||
authorName = authorName,
|
||||
roundedCorner = roundedCorner,
|
||||
contentScale = contentScale,
|
||||
nostrUriCallback = nostrUriCallback,
|
||||
isLiveStream = isLiveStream,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun UrlPreview(
|
||||
url: String,
|
||||
urlText: String,
|
||||
callbackUri: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav?,
|
||||
) = AppLoadUrlPreview(
|
||||
url = url,
|
||||
urlText = urlText,
|
||||
callbackUri = callbackUri,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun LocationPreviewMap(
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
aspectRatio: Float,
|
||||
pinColor: Color?,
|
||||
pinEmoji: String?,
|
||||
pinAlpha: Float,
|
||||
) = AppLocationPreviewMap(
|
||||
latitude = latitude,
|
||||
longitude = longitude,
|
||||
aspectRatio = aspectRatio,
|
||||
pinColor = pinColor,
|
||||
pinEmoji = pinEmoji,
|
||||
pinAlpha = pinAlpha,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun LoadCityName(
|
||||
geohashStr: String,
|
||||
onLoading: (@Composable () -> Unit)?,
|
||||
content: @Composable (String) -> Unit,
|
||||
) = AppLoadCityName(
|
||||
geohashStr = geohashStr,
|
||||
onLoading = onLoading,
|
||||
content = content,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderAudioTrack(
|
||||
note: Note,
|
||||
contentScale: ContentScale,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderAudioTrack(
|
||||
note = note,
|
||||
contentScale = contentScale,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderMusicTrack(
|
||||
note: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderMusicTrack(
|
||||
note = note,
|
||||
makeItShort = makeItShort,
|
||||
canPreview = canPreview,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun PodcastEpisodeAudioPlayer(
|
||||
audio: PodcastAudio,
|
||||
note: Note,
|
||||
title: String?,
|
||||
image: String?,
|
||||
borderModifier: Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
) = AppPodcastEpisodeAudioPlayer(
|
||||
audio = audio,
|
||||
note = note,
|
||||
title = title,
|
||||
image = image,
|
||||
borderModifier = borderModifier,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderVoiceTrack(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderVoiceTrack(
|
||||
note = note,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderAudioFromIMeta(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderAudioFromIMeta(
|
||||
note = note,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderAudioWithWaveform(
|
||||
mediaUrl: String,
|
||||
title: String?,
|
||||
mimeType: String?,
|
||||
waveform: WaveformData?,
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderAudioWithWaveform(
|
||||
mediaUrl = mediaUrl,
|
||||
title = title,
|
||||
mimeType = mimeType,
|
||||
waveform = waveform,
|
||||
note = note,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderChessGame(
|
||||
note: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderChessGame(
|
||||
note = note,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderLiveChessChallenge(
|
||||
note: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderLiveChessChallenge(
|
||||
note = note,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderLiveChessGameEnd(
|
||||
note: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderLiveChessGameEnd(
|
||||
note = note,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderGitRepositoryEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderGitRepositoryEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderGitPatchEvent(
|
||||
baseNote: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderGitPatchEvent(
|
||||
baseNote = baseNote,
|
||||
makeItShort = makeItShort,
|
||||
canPreview = canPreview,
|
||||
quotesLeft = quotesLeft,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderGitIssueEvent(
|
||||
baseNote: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderGitIssueEvent(
|
||||
baseNote = baseNote,
|
||||
makeItShort = makeItShort,
|
||||
canPreview = canPreview,
|
||||
quotesLeft = quotesLeft,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderGitPullRequestEvent(
|
||||
baseNote: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderGitPullRequestEvent(
|
||||
baseNote = baseNote,
|
||||
makeItShort = makeItShort,
|
||||
canPreview = canPreview,
|
||||
quotesLeft = quotesLeft,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderGitPullRequestUpdateEvent(
|
||||
baseNote: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderGitPullRequestUpdateEvent(
|
||||
baseNote = baseNote,
|
||||
makeItShort = makeItShort,
|
||||
canPreview = canPreview,
|
||||
quotesLeft = quotesLeft,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderMeetingSpaceEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderMeetingSpaceEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderMeetingRoomEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderMeetingRoomEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderMeetingRoomPresence(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderMeetingRoomPresence(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderRootNappletEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderRootNappletEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderNamedNappletEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderNamedNappletEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderRootSiteEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderRootSiteEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun RenderNamedSiteEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppRenderNamedSiteEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun ReactionsRow(
|
||||
baseNote: Note,
|
||||
showReactionDetail: Boolean,
|
||||
addPadding: Boolean,
|
||||
editState: State<GenericLoadable<EditState>>?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppReactionsRow(
|
||||
baseNote = baseNote,
|
||||
showReactionDetail = showReactionDetail,
|
||||
addPadding = addPadding,
|
||||
editState = editState,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun LikeReaction(
|
||||
baseNote: Note,
|
||||
grayTint: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
iconSize: Dp,
|
||||
heartSizeModifier: Modifier,
|
||||
iconFontSize: TextUnit,
|
||||
showCounter: Boolean,
|
||||
) = AppLikeReaction(
|
||||
baseNote = baseNote,
|
||||
grayTint = grayTint,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
iconSize = iconSize,
|
||||
heartSizeModifier = heartSizeModifier,
|
||||
iconFontSize = iconFontSize,
|
||||
showCounter = showCounter,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun ZapReaction(
|
||||
baseNote: Note,
|
||||
grayTint: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
iconSize: Dp,
|
||||
iconSizeModifier: Modifier,
|
||||
animationModifier: Modifier,
|
||||
showCounter: Boolean,
|
||||
nav: INav,
|
||||
) = AppZapReaction(
|
||||
baseNote = baseNote,
|
||||
grayTint = grayTint,
|
||||
accountViewModel = accountViewModel,
|
||||
iconSize = iconSize,
|
||||
iconSizeModifier = iconSizeModifier,
|
||||
animationModifier = animationModifier,
|
||||
showCounter = showCounter,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun EditPostView(
|
||||
onClose: () -> Unit,
|
||||
edit: Note,
|
||||
versionLookingAt: Note?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = AppEditPostView(
|
||||
onClose = onClose,
|
||||
edit = edit,
|
||||
versionLookingAt = versionLookingAt,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,372 @@
|
||||
/*
|
||||
* 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.platform
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
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.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.types.EditState
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import com.vitorpamplona.quartz.podcasts.PodcastAudio
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* The platform halves of the shared note renderer: what a note card needs that only the front end
|
||||
* can draw or do. Media players and the zoomable viewer, maps and reverse geocoding, the note types
|
||||
* built on a platform engine (audio players, chess, the git browser, meeting rooms, napplets), the
|
||||
* reactions/zap row with its wallet hand-offs, and the post editor.
|
||||
*
|
||||
* The shared note code calls these through [LocalNotePlatform]; a front end installs its own at the
|
||||
* composition root (Android does in its theme). Every member defaults to drawing nothing, so a
|
||||
* preview, or a front end still wiring a piece, gets an empty slot rather than a crash.
|
||||
*/
|
||||
@Stable
|
||||
interface NotePlatform {
|
||||
// Media
|
||||
|
||||
@Composable
|
||||
fun ZoomableContentView(
|
||||
content: BaseMediaContent,
|
||||
images: ImmutableList<BaseMediaContent>,
|
||||
roundedCorner: Boolean,
|
||||
contentScale: ContentScale,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun ZoomableImageDialog(
|
||||
imageUrl: BaseMediaContent,
|
||||
allImages: ImmutableList<BaseMediaContent>,
|
||||
sourceBounds: Rect?,
|
||||
onDismiss: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun GifVideoView(
|
||||
videoUri: String,
|
||||
contentDescription: String?,
|
||||
dimensions: DimensionTag?,
|
||||
blurhash: String?,
|
||||
roundedCorner: Boolean,
|
||||
contentScale: ContentScale,
|
||||
onDialog: (() -> Unit)?,
|
||||
accountViewModel: AccountViewModel,
|
||||
thumbhash: String?,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun VideoView(
|
||||
videoUri: String,
|
||||
mimeType: String?,
|
||||
title: String?,
|
||||
artworkUri: String?,
|
||||
authorName: String?,
|
||||
roundedCorner: Boolean,
|
||||
contentScale: ContentScale,
|
||||
nostrUriCallback: String?,
|
||||
isLiveStream: Boolean,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {}
|
||||
|
||||
/** The link card for [url]: OpenGraph preview, inline media, or a plain link. */
|
||||
@Composable
|
||||
fun UrlPreview(
|
||||
url: String,
|
||||
urlText: String,
|
||||
callbackUri: String?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav?,
|
||||
) {}
|
||||
|
||||
// Places
|
||||
|
||||
@Composable
|
||||
fun LocationPreviewMap(
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
aspectRatio: Float,
|
||||
pinColor: Color?,
|
||||
pinEmoji: String?,
|
||||
pinAlpha: Float,
|
||||
) {}
|
||||
|
||||
/** Resolves [geohashStr] to a place name for [content], showing [onLoading] meanwhile. */
|
||||
@Composable
|
||||
fun LoadCityName(
|
||||
geohashStr: String,
|
||||
onLoading: (@Composable () -> Unit)?,
|
||||
content: @Composable (String) -> Unit,
|
||||
) {}
|
||||
|
||||
// Audio
|
||||
|
||||
@Composable
|
||||
fun RenderAudioTrack(
|
||||
note: Note,
|
||||
contentScale: ContentScale,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderMusicTrack(
|
||||
note: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun PodcastEpisodeAudioPlayer(
|
||||
audio: PodcastAudio,
|
||||
note: Note,
|
||||
title: String?,
|
||||
image: String?,
|
||||
borderModifier: Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderVoiceTrack(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderAudioFromIMeta(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderAudioWithWaveform(
|
||||
mediaUrl: String,
|
||||
title: String?,
|
||||
mimeType: String?,
|
||||
waveform: WaveformData?,
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
// Chess
|
||||
|
||||
@Composable
|
||||
fun RenderChessGame(
|
||||
note: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderLiveChessChallenge(
|
||||
note: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderLiveChessGameEnd(
|
||||
note: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
// Git
|
||||
|
||||
@Composable
|
||||
fun RenderGitRepositoryEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderGitPatchEvent(
|
||||
baseNote: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderGitIssueEvent(
|
||||
baseNote: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderGitPullRequestEvent(
|
||||
baseNote: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderGitPullRequestUpdateEvent(
|
||||
baseNote: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
// Meeting rooms (audio spaces)
|
||||
|
||||
@Composable
|
||||
fun RenderMeetingSpaceEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderMeetingRoomEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderMeetingRoomPresence(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
// Napplets and nsites
|
||||
|
||||
@Composable
|
||||
fun RenderRootNappletEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderNamedNappletEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderRootSiteEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun RenderNamedSiteEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
// Reactions, zaps and editing
|
||||
|
||||
@Composable
|
||||
fun ReactionsRow(
|
||||
baseNote: Note,
|
||||
showReactionDetail: Boolean,
|
||||
addPadding: Boolean,
|
||||
editState: State<GenericLoadable<EditState>>?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun LikeReaction(
|
||||
baseNote: Note,
|
||||
grayTint: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
iconSize: Dp,
|
||||
heartSizeModifier: Modifier,
|
||||
iconFontSize: TextUnit,
|
||||
showCounter: Boolean,
|
||||
) {}
|
||||
|
||||
@Composable
|
||||
fun ZapReaction(
|
||||
baseNote: Note,
|
||||
grayTint: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
iconSize: Dp,
|
||||
iconSizeModifier: Modifier,
|
||||
animationModifier: Modifier,
|
||||
showCounter: Boolean,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
/** The editor for a new version of [edit], shown while [versionLookingAt] is on screen. */
|
||||
@Composable
|
||||
fun EditPostView(
|
||||
onClose: () -> Unit,
|
||||
edit: Note,
|
||||
versionLookingAt: Note?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {}
|
||||
|
||||
/** Draws nothing: previews, and front ends still wiring their pieces. */
|
||||
object None : NotePlatform
|
||||
}
|
||||
|
||||
/** The front end's [NotePlatform]. Static: it is set once at the root and never changes. */
|
||||
val LocalNotePlatform = staticCompositionLocalOf<NotePlatform> { NotePlatform.None }
|
||||
+538
@@ -0,0 +1,538 @@
|
||||
/*
|
||||
* 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.platform
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
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.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Font14SP
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size14Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size18Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size18dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size20dp
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.types.EditState
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import com.vitorpamplona.quartz.podcasts.PodcastAudio
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
// Call-site shims for [NotePlatform]: the same names and defaults the app's own composables have,
|
||||
// so shared note code reads as it did and only its imports point here.
|
||||
|
||||
@Composable
|
||||
fun ZoomableContentView(
|
||||
content: BaseMediaContent,
|
||||
images: ImmutableList<BaseMediaContent> = remember(content) { persistentListOf(content) },
|
||||
roundedCorner: Boolean,
|
||||
contentScale: ContentScale,
|
||||
accountViewModel: AccountViewModel,
|
||||
) = LocalNotePlatform.current.ZoomableContentView(
|
||||
content = content,
|
||||
images = images,
|
||||
roundedCorner = roundedCorner,
|
||||
contentScale = contentScale,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ZoomableImageDialog(
|
||||
imageUrl: BaseMediaContent,
|
||||
allImages: ImmutableList<BaseMediaContent> = remember(imageUrl) { persistentListOf(imageUrl) },
|
||||
sourceBounds: Rect? = null,
|
||||
onDismiss: () -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
) = LocalNotePlatform.current.ZoomableImageDialog(
|
||||
imageUrl = imageUrl,
|
||||
allImages = allImages,
|
||||
sourceBounds = sourceBounds,
|
||||
onDismiss = onDismiss,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun GifVideoView(
|
||||
videoUri: String,
|
||||
contentDescription: String?,
|
||||
dimensions: DimensionTag?,
|
||||
blurhash: String?,
|
||||
roundedCorner: Boolean,
|
||||
contentScale: ContentScale,
|
||||
onDialog: (() -> Unit)? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
thumbhash: String? = null,
|
||||
) = LocalNotePlatform.current.GifVideoView(
|
||||
videoUri = videoUri,
|
||||
contentDescription = contentDescription,
|
||||
dimensions = dimensions,
|
||||
blurhash = blurhash,
|
||||
roundedCorner = roundedCorner,
|
||||
contentScale = contentScale,
|
||||
onDialog = onDialog,
|
||||
accountViewModel = accountViewModel,
|
||||
thumbhash = thumbhash,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun VideoView(
|
||||
videoUri: String,
|
||||
mimeType: String?,
|
||||
title: String? = null,
|
||||
artworkUri: String? = null,
|
||||
authorName: String? = null,
|
||||
roundedCorner: Boolean,
|
||||
contentScale: ContentScale,
|
||||
nostrUriCallback: String? = null,
|
||||
isLiveStream: Boolean = false,
|
||||
accountViewModel: AccountViewModel,
|
||||
) = LocalNotePlatform.current.VideoView(
|
||||
videoUri = videoUri,
|
||||
mimeType = mimeType,
|
||||
title = title,
|
||||
artworkUri = artworkUri,
|
||||
authorName = authorName,
|
||||
roundedCorner = roundedCorner,
|
||||
contentScale = contentScale,
|
||||
nostrUriCallback = nostrUriCallback,
|
||||
isLiveStream = isLiveStream,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun LoadUrlPreview(
|
||||
url: String,
|
||||
urlText: String,
|
||||
callbackUri: String? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav? = null,
|
||||
) = LocalNotePlatform.current.UrlPreview(
|
||||
url = url,
|
||||
urlText = urlText,
|
||||
callbackUri = callbackUri,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun LocationPreviewMap(
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
aspectRatio: Float = 1f,
|
||||
pinColor: Color? = null,
|
||||
pinEmoji: String? = null,
|
||||
pinAlpha: Float = 1f,
|
||||
) = LocalNotePlatform.current.LocationPreviewMap(
|
||||
latitude = latitude,
|
||||
longitude = longitude,
|
||||
aspectRatio = aspectRatio,
|
||||
pinColor = pinColor,
|
||||
pinEmoji = pinEmoji,
|
||||
pinAlpha = pinAlpha,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun LoadCityName(
|
||||
geohashStr: String,
|
||||
onLoading: (@Composable () -> Unit)? = null,
|
||||
content: @Composable (String) -> Unit,
|
||||
) = LocalNotePlatform.current.LoadCityName(
|
||||
geohashStr = geohashStr,
|
||||
onLoading = onLoading,
|
||||
content = content,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderAudioTrack(
|
||||
note: Note,
|
||||
contentScale: ContentScale,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderAudioTrack(
|
||||
note = note,
|
||||
contentScale = contentScale,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderMusicTrack(
|
||||
note: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderMusicTrack(
|
||||
note = note,
|
||||
makeItShort = makeItShort,
|
||||
canPreview = canPreview,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun PodcastEpisodeAudioPlayer(
|
||||
audio: PodcastAudio,
|
||||
note: Note,
|
||||
title: String?,
|
||||
image: String?,
|
||||
borderModifier: Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
) = LocalNotePlatform.current.PodcastEpisodeAudioPlayer(
|
||||
audio = audio,
|
||||
note = note,
|
||||
title = title,
|
||||
image = image,
|
||||
borderModifier = borderModifier,
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderVoiceTrack(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderVoiceTrack(
|
||||
note = note,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderAudioFromIMeta(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderAudioFromIMeta(
|
||||
note = note,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderAudioWithWaveform(
|
||||
mediaUrl: String,
|
||||
title: String?,
|
||||
mimeType: String?,
|
||||
waveform: WaveformData?,
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderAudioWithWaveform(
|
||||
mediaUrl = mediaUrl,
|
||||
title = title,
|
||||
mimeType = mimeType,
|
||||
waveform = waveform,
|
||||
note = note,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderChessGame(
|
||||
note: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderChessGame(
|
||||
note = note,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderLiveChessChallenge(
|
||||
note: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderLiveChessChallenge(
|
||||
note = note,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderLiveChessGameEnd(
|
||||
note: Note,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderLiveChessGameEnd(
|
||||
note = note,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderGitRepositoryEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderGitRepositoryEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderGitPatchEvent(
|
||||
baseNote: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderGitPatchEvent(
|
||||
baseNote = baseNote,
|
||||
makeItShort = makeItShort,
|
||||
canPreview = canPreview,
|
||||
quotesLeft = quotesLeft,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderGitIssueEvent(
|
||||
baseNote: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderGitIssueEvent(
|
||||
baseNote = baseNote,
|
||||
makeItShort = makeItShort,
|
||||
canPreview = canPreview,
|
||||
quotesLeft = quotesLeft,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderGitPullRequestEvent(
|
||||
baseNote: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderGitPullRequestEvent(
|
||||
baseNote = baseNote,
|
||||
makeItShort = makeItShort,
|
||||
canPreview = canPreview,
|
||||
quotesLeft = quotesLeft,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderGitPullRequestUpdateEvent(
|
||||
baseNote: Note,
|
||||
makeItShort: Boolean,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderGitPullRequestUpdateEvent(
|
||||
baseNote = baseNote,
|
||||
makeItShort = makeItShort,
|
||||
canPreview = canPreview,
|
||||
quotesLeft = quotesLeft,
|
||||
backgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderMeetingSpaceEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderMeetingSpaceEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderMeetingRoomEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderMeetingRoomEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderMeetingRoomPresence(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderMeetingRoomPresence(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderRootNappletEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderRootNappletEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderNamedNappletEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderNamedNappletEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderRootSiteEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderRootSiteEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RenderNamedSiteEvent(
|
||||
baseNote: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.RenderNamedSiteEvent(
|
||||
baseNote = baseNote,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ReactionsRow(
|
||||
baseNote: Note,
|
||||
showReactionDetail: Boolean,
|
||||
addPadding: Boolean,
|
||||
editState: State<GenericLoadable<EditState>>?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.ReactionsRow(
|
||||
baseNote = baseNote,
|
||||
showReactionDetail = showReactionDetail,
|
||||
addPadding = addPadding,
|
||||
editState = editState,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun LikeReaction(
|
||||
baseNote: Note,
|
||||
grayTint: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
iconSize: Dp = Size18dp,
|
||||
heartSizeModifier: Modifier = Size18Modifier,
|
||||
iconFontSize: TextUnit = Font14SP,
|
||||
showCounter: Boolean = true,
|
||||
) = LocalNotePlatform.current.LikeReaction(
|
||||
baseNote = baseNote,
|
||||
grayTint = grayTint,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
iconSize = iconSize,
|
||||
heartSizeModifier = heartSizeModifier,
|
||||
iconFontSize = iconFontSize,
|
||||
showCounter = showCounter,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ZapReaction(
|
||||
baseNote: Note,
|
||||
grayTint: Color,
|
||||
accountViewModel: AccountViewModel,
|
||||
iconSize: Dp = Size20dp,
|
||||
iconSizeModifier: Modifier = Size20Modifier,
|
||||
animationModifier: Modifier = Size14Modifier,
|
||||
showCounter: Boolean = true,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.ZapReaction(
|
||||
baseNote = baseNote,
|
||||
grayTint = grayTint,
|
||||
accountViewModel = accountViewModel,
|
||||
iconSize = iconSize,
|
||||
iconSizeModifier = iconSizeModifier,
|
||||
animationModifier = animationModifier,
|
||||
showCounter = showCounter,
|
||||
nav = nav,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun EditPostView(
|
||||
onClose: () -> Unit,
|
||||
edit: Note,
|
||||
versionLookingAt: Note?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = LocalNotePlatform.current.EditPostView(
|
||||
onClose = onClose,
|
||||
edit = edit,
|
||||
versionLookingAt = versionLookingAt,
|
||||
accountViewModel = accountViewModel,
|
||||
nav = nav,
|
||||
)
|
||||
+1
-1
@@ -30,7 +30,7 @@ import com.vitorpamplona.amethyst.commons.model.ZapAmountCommentNotification
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip61Nutzaps.nutzap.NutzapEvent
|
||||
|
||||
@@ -77,7 +77,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil3.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
@@ -111,6 +110,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.LinkIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadUser
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.commons.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size16Modifier
|
||||
@@ -118,7 +118,6 @@ 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.painterRes
|
||||
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
|
||||
@@ -193,7 +192,7 @@ fun RenderAppDefinition(
|
||||
}
|
||||
} else {
|
||||
Image(
|
||||
painter = painterRes(R.drawable.profile_banner, 6),
|
||||
painter = painterRes(Res.drawable.profile_banner, 6),
|
||||
contentDescription = stringRes(id = Res.string.profile_banner),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier =
|
||||
|
||||
@@ -44,7 +44,6 @@ 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.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -72,18 +71,19 @@ import com.vitorpamplona.amethyst.commons.resources.attestor_recommendation_for_
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.DateTimeStyle
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadUser
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.formatDateTime
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.formatMediumDate
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.KindChip
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.AttestationEvent
|
||||
import com.vitorpamplona.quartz.experimental.attestations.attestation.tags.AttestationStatus
|
||||
@@ -199,19 +199,18 @@ fun RenderAttestation(
|
||||
}
|
||||
|
||||
if (validFrom != null || validTo != null) {
|
||||
val context = LocalContext.current
|
||||
Spacer(modifier = DoubleVertSpacer)
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
validFrom?.let {
|
||||
Text(
|
||||
text = stringRes(Res.string.attestation_valid_from, formatMediumDate(it, context)),
|
||||
text = stringRes(Res.string.attestation_valid_from, formatDateTime(it * 1000, DateTimeStyle.MEDIUM, DateTimeStyle.NONE)),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
validTo?.let {
|
||||
Text(
|
||||
text = stringRes(Res.string.attestation_valid_to, formatMediumDate(it, context)),
|
||||
text = stringRes(Res.string.attestation_valid_to, formatDateTime(it * 1000, DateTimeStyle.MEDIUM, DateTimeStyle.NONE)),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
||||
@@ -41,6 +41,7 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
@@ -52,7 +53,6 @@ import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.LoadThumbAndThenVideoView
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.VideoView
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
|
||||
@@ -38,10 +38,10 @@ import com.vitorpamplona.amethyst.commons.ui.note.UserPicture
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ZapIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size25dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor
|
||||
import com.vitorpamplona.amethyst.commons.util.PlatformNumberFormatter
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.CrossfadeToDisplayComment
|
||||
import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent
|
||||
import java.text.NumberFormat
|
||||
|
||||
/**
|
||||
* Standalone card for a NIP-B1 BOLT12 zap (kind 9736), styled like the NIP-57
|
||||
@@ -91,7 +91,7 @@ fun RenderBolt12Zap(
|
||||
|
||||
RenderZappedPost(note, quotesLeft, cardBackground, accountViewModel, nav)
|
||||
|
||||
amountSats?.let { ActivityAmountRow(NumberFormat.getNumberInstance().format(it), orange) }
|
||||
amountSats?.let { ActivityAmountRow(PlatformNumberFormatter().format(it), orange) }
|
||||
|
||||
comment?.let {
|
||||
CrossfadeToDisplayComment(it, cardBackground, nav, accountViewModel)
|
||||
|
||||
@@ -42,20 +42,19 @@ import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.preview_card_image_for
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.DateTimeStyle
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.formatDateTime
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
||||
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.elements.DefaultImageHeader
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeaderBackground
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip52Calendar.appt.day.CalendarDateSlotEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.appt.time.CalendarTimeSlotEvent
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
@Composable
|
||||
fun RenderCalendarTimeSlotEvent(
|
||||
@@ -239,11 +238,9 @@ private fun formatTimestampRange(
|
||||
end: Long?,
|
||||
): String? {
|
||||
if (start == null) return null
|
||||
val formatter = SimpleDateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT)
|
||||
val startStr = formatter.format(Date(start * 1000))
|
||||
val startStr = formatDateTime(start * 1000, DateTimeStyle.MEDIUM, DateTimeStyle.SHORT)
|
||||
return if (end != null && end != start) {
|
||||
val endFormatter = SimpleDateFormat.getTimeInstance(DateFormat.SHORT)
|
||||
"$startStr – ${endFormatter.format(Date(end * 1000))}"
|
||||
"$startStr – ${formatDateTime(end * 1000, DateTimeStyle.NONE, DateTimeStyle.SHORT)}"
|
||||
} else {
|
||||
startStr
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.LocalRichTextPlatform
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.grayText
|
||||
@@ -51,7 +52,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.markdown.RenderContentAsMarkdown
|
||||
import com.vitorpamplona.quartz.experimental.citations.CitationEvent
|
||||
import com.vitorpamplona.quartz.experimental.citations.ExternalCitationEvent
|
||||
import com.vitorpamplona.quartz.experimental.citations.HardcopyCitationEvent
|
||||
@@ -137,7 +137,7 @@ fun RenderCitation(
|
||||
// A prompt citation's body is the prompt itself, which the reference implementation
|
||||
// renders as Markdown; the other two carry a plain note.
|
||||
if (noteEvent is PromptCitationEvent) {
|
||||
RenderContentAsMarkdown(
|
||||
LocalRichTextPlatform.current.Markdown(
|
||||
content = noteEvent.content,
|
||||
tags = tags,
|
||||
canPreview = canPreview && !makeItShort,
|
||||
|
||||
@@ -51,6 +51,7 @@ 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.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
import com.vitorpamplona.quartz.utils.parseBigDecimalOrNull
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@@ -137,7 +138,7 @@ fun RenderClassifieds(
|
||||
val priceTag =
|
||||
remember(noteEvent) {
|
||||
val newAmount =
|
||||
price.amount.toBigDecimalOrNull()?.let { showAmount(it) }
|
||||
parseBigDecimalOrNull(price.amount)?.let { showAmount(it) }
|
||||
?: price.amount
|
||||
|
||||
if (price.frequency != null && price.currency != null) {
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.types
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -48,7 +47,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@@ -80,6 +78,7 @@ import com.vitorpamplona.amethyst.commons.resources.quick_action_share
|
||||
import com.vitorpamplona.amethyst.commons.resources.quick_action_share_browser_link
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RichTextViewer
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.rememberTextSharer
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture
|
||||
@@ -542,27 +541,11 @@ fun ShareCommunityButton(
|
||||
) {
|
||||
val quickActionShareBrowserLinkStr = stringRes(Res.string.quick_action_share_browser_link)
|
||||
val quickActionShareStr = stringRes(Res.string.quick_action_share)
|
||||
val actContext = LocalContext.current
|
||||
val sharer = rememberTextSharer()
|
||||
|
||||
FilledTonalIconButton(
|
||||
onClick = {
|
||||
val sendIntent =
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
type = "text/plain"
|
||||
putExtra(
|
||||
Intent.EXTRA_TEXT,
|
||||
externalLinkForNote(note),
|
||||
)
|
||||
putExtra(
|
||||
Intent.EXTRA_TITLE,
|
||||
quickActionShareBrowserLinkStr,
|
||||
)
|
||||
}
|
||||
|
||||
val shareIntent =
|
||||
Intent.createChooser(sendIntent, quickActionShareStr)
|
||||
actContext.startActivity(shareIntent)
|
||||
sharer.share(externalLinkForNote(note), quickActionShareBrowserLinkStr, quickActionShareStr)
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
|
||||
@@ -44,8 +44,8 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.SpacedBy5dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.GalleryUnloaded
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNote
|
||||
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaLocalImage
|
||||
@@ -38,7 +38,6 @@ import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import java.io.File
|
||||
|
||||
@Composable
|
||||
fun FileStorageHeaderDisplay(
|
||||
@@ -74,7 +73,7 @@ private fun ObserverAndRenderNIP95(
|
||||
// Creates a new object when the event arrives to force an update of the image.
|
||||
val note = noteState.note
|
||||
val uri = header.toNostrUri()
|
||||
val localDir = note.idHex.let { File(Amethyst.instance.nip95cache, it) }
|
||||
val localDir = LocalCache.appHost.nip95Blobs?.path(note.idHex)
|
||||
val blurHash = eventHeader.blurhash()
|
||||
val thumbHash = eventHeader.thumbhash()
|
||||
val dimensions = eventHeader.dimensions()
|
||||
|
||||
@@ -36,7 +36,6 @@ 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.service.location.LocationState
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.location.LocationPreviewMap
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.toGeoHash
|
||||
import com.vitorpamplona.quartz.nipCCGeocaching.foundLog.GeocacheFoundLogEvent
|
||||
@@ -117,7 +116,7 @@ fun distanceToCache(
|
||||
/**
|
||||
* A distance formatted no finer than its source can support.
|
||||
*
|
||||
* The reader's position comes from [LocationState.geohashStateFlow], which is a **5-character
|
||||
* The reader's position comes from `LocationState.geohashStateFlow`, which is a **5-character
|
||||
* geohash** — a 4.89km × 4.89km cell — so the centroid this measures from can be ~3.5km from
|
||||
* where the reader is standing. Printing metres off that would be fiction: someone standing at
|
||||
* the cache would read "3 km", and a cache 2km away could read "120 m". Anything inside one
|
||||
|
||||
@@ -61,8 +61,9 @@ import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeaderBackground
|
||||
import com.vitorpamplona.quartz.nip75ZapGoals.ZapGoalEvent
|
||||
import com.vitorpamplona.quartz.utils.BigDecimal
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.math.BigDecimal
|
||||
import com.vitorpamplona.quartz.utils.toDoubleValue
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
@@ -149,12 +150,12 @@ fun GoalProgressBar(
|
||||
val zapsState by observeNoteZaps(note, accountViewModel)
|
||||
|
||||
var zapraiserStatus by
|
||||
remember { mutableStateOf(ZapraiserStatus(0F, showAmount(goalAmountSats.toBigDecimal()))) }
|
||||
remember { mutableStateOf(ZapraiserStatus(0F, showAmount(BigDecimal(goalAmountSats)))) }
|
||||
|
||||
LaunchedEffect(key1 = zapsState) {
|
||||
zapsState?.note?.let {
|
||||
val newZapAmount = accountViewModel.account.zaps.calculateZappedAmount(note)
|
||||
var percentage = newZapAmount.div(goalAmountSats.toBigDecimal()).toFloat()
|
||||
var percentage = (newZapAmount.toDoubleValue() / goalAmountSats).toFloat()
|
||||
if (percentage > 1) percentage = 1f
|
||||
|
||||
val left =
|
||||
@@ -162,7 +163,7 @@ fun GoalProgressBar(
|
||||
"0"
|
||||
} else {
|
||||
showAmount(
|
||||
goalAmountSats.toBigDecimal() * BigDecimal(1.0 - percentage),
|
||||
BigDecimal(goalAmountSats * (1.0 - percentage)),
|
||||
)
|
||||
}
|
||||
zapraiserStatus = ZapraiserStatus(percentage, left)
|
||||
@@ -193,7 +194,7 @@ fun GoalProgressBar(
|
||||
stringRes(
|
||||
Res.string.goal_progress,
|
||||
totalPercentage,
|
||||
showAmount(goalAmountSats.toBigDecimal()),
|
||||
showAmount(BigDecimal(goalAmountSats)),
|
||||
),
|
||||
fontSize = Font14SP,
|
||||
maxLines = 1,
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.types
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
@@ -66,11 +65,11 @@ import com.vitorpamplona.amethyst.commons.ui.note.HighlightedQuote
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CreateClickableTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.rememberTranslation
|
||||
import com.vitorpamplona.amethyst.ui.note.nip22Comments.DisplayExternalId
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.kindName
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.firstTagValueFor
|
||||
@@ -80,9 +79,9 @@ import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import com.vitorpamplona.quartz.utils.Rfc3986
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.net.URL
|
||||
|
||||
@Composable
|
||||
fun RenderHighlight(
|
||||
@@ -285,10 +284,31 @@ private fun buildTextFragmentUrl(
|
||||
): String {
|
||||
val separator = if (baseUrl.contains("#")) "&" else "#"
|
||||
|
||||
val prefixPart = trimToFragmentEdge(prefix, keepStart = false)?.let { "${Uri.encode(it)}-," } ?: ""
|
||||
val suffixPart = trimToFragmentEdge(suffix, keepStart = true)?.let { ",-${Uri.encode(it)}" } ?: ""
|
||||
val prefixPart = trimToFragmentEdge(prefix, keepStart = false)?.let { "${encodeFragmentComponent(it)}-," } ?: ""
|
||||
val suffixPart = trimToFragmentEdge(suffix, keepStart = true)?.let { ",-${encodeFragmentComponent(it)}" } ?: ""
|
||||
|
||||
return "$baseUrl$separator:~:text=$prefixPart${Uri.encode(exact)}$suffixPart"
|
||||
return "$baseUrl$separator:~:text=$prefixPart${encodeFragmentComponent(exact)}$suffixPart"
|
||||
}
|
||||
|
||||
private const val FRAGMENT_SAFE = "_-!.~'()*"
|
||||
private val HEX_DIGITS = "0123456789ABCDEF".toCharArray()
|
||||
|
||||
/**
|
||||
* Percent-encodes [text] as UTF-8, leaving only letters, digits and `_-!.~'()*` alone: what
|
||||
* Android's `Uri.encode` does, so spaces become `%20` (not `+`) and `-`, `,` and `&` cannot be
|
||||
* mistaken for text-fragment syntax.
|
||||
*/
|
||||
private fun encodeFragmentComponent(text: String): String {
|
||||
val out = StringBuilder(text.length)
|
||||
for (byte in text.encodeToByteArray()) {
|
||||
val c = (byte.toInt() and 0xFF).toChar()
|
||||
if (c < '\u0080' && (c.isLetterOrDigit() || c in FRAGMENT_SAFE)) {
|
||||
out.append(c)
|
||||
} else {
|
||||
out.append('%').append(HEX_DIGITS[(byte.toInt() shr 4) and 0xF]).append(HEX_DIGITS[byte.toInt() and 0xF])
|
||||
}
|
||||
}
|
||||
return out.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -502,14 +522,14 @@ fun DisplayEntryForAUrl(
|
||||
val validatedUrl =
|
||||
remember(url) {
|
||||
try {
|
||||
URL(url)
|
||||
Rfc3986.host(url)
|
||||
} catch (_: Exception) {
|
||||
Log.w("Note Compose") { "Invalid URI: $url" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
validatedUrl?.host?.let { host ->
|
||||
validatedUrl?.let { host ->
|
||||
if (userBase != null) {
|
||||
Text("-", maxLines = 1)
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.live_stream_has_ended
|
||||
import com.vitorpamplona.amethyst.commons.resources.live_stream_is_offline
|
||||
import com.vitorpamplona.amethyst.commons.resources.preview_card_image_for
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CheckIfVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeCheckIfVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
@@ -69,14 +71,12 @@ import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier
|
||||
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.screen.loggedIn.home.CheckIfVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CrossfadeCheckIfVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTag
|
||||
|
||||
@@ -56,6 +56,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.util.DecimalPatternFormatter
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
@@ -63,9 +64,6 @@ import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.DecimalFormat
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
import kotlin.math.abs
|
||||
|
||||
@Preview
|
||||
@@ -258,7 +256,7 @@ fun RenderEyeGlassesPrescription(
|
||||
visionPrescription.status?.let {
|
||||
Text(
|
||||
text = "Status: ${it.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
|
||||
if (it.isLowerCase()) it.titlecase() else it.toString()
|
||||
}}",
|
||||
modifier = Modifier.padding(4.dp).fillMaxWidth(),
|
||||
)
|
||||
@@ -350,9 +348,9 @@ fun RenderEyeGlassesPrescriptionHeaderRow() {
|
||||
|
||||
@Composable
|
||||
fun RenderEyeGlassesPrescriptionRow(data: LensSpecification) {
|
||||
val numberFormat = DecimalFormat("##.00")
|
||||
val pdFormat = DecimalFormat("##.0")
|
||||
val integerFormat = DecimalFormat("###")
|
||||
val numberFormat = DecimalPatternFormatter("##.00")
|
||||
val pdFormat = DecimalPatternFormatter("##.0")
|
||||
val integerFormat = DecimalPatternFormatter("###")
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
|
||||
@@ -362,7 +360,7 @@ fun RenderEyeGlassesPrescriptionRow(data: LensSpecification) {
|
||||
Text(
|
||||
text =
|
||||
data.eye?.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
|
||||
if (it.isLowerCase()) it.titlecase() else it.toString()
|
||||
} ?: "Unknown",
|
||||
modifier = Modifier.padding(4.dp).weight(1f),
|
||||
)
|
||||
@@ -510,8 +508,8 @@ fun RenderEyeContactsPrescriptionHeaderRow() {
|
||||
|
||||
@Composable
|
||||
fun RenderEyeContactsPrescriptionRow(data: LensSpecification) {
|
||||
val numberFormat = DecimalFormat("##.00")
|
||||
val integerFormat = DecimalFormat("###")
|
||||
val numberFormat = DecimalPatternFormatter("##.00")
|
||||
val integerFormat = DecimalPatternFormatter("###")
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
|
||||
@@ -521,7 +519,7 @@ fun RenderEyeContactsPrescriptionRow(data: LensSpecification) {
|
||||
Text(
|
||||
text =
|
||||
data.eye?.replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
|
||||
if (it.isLowerCase()) it.titlecase() else it.toString()
|
||||
} ?: "Unknown",
|
||||
modifier = Modifier.padding(4.dp).weight(1f),
|
||||
)
|
||||
@@ -626,7 +624,7 @@ fun RenderEyeContactsPrescriptionRow(data: LensSpecification) {
|
||||
|
||||
fun formatOrBlank(
|
||||
amount: Double?,
|
||||
numberFormat: NumberFormat,
|
||||
numberFormat: DecimalPatternFormatter,
|
||||
): String {
|
||||
if (amount == null) return ""
|
||||
if (abs(amount) < 0.01) return ""
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -41,11 +42,11 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.net.toUri
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
@@ -68,11 +69,14 @@ import com.vitorpamplona.amethyst.commons.resources.nest_presence_listening
|
||||
import com.vitorpamplona.amethyst.commons.resources.nest_presence_on_stage
|
||||
import com.vitorpamplona.amethyst.commons.resources.nest_presence_raised_hand
|
||||
import com.vitorpamplona.amethyst.commons.resources.nest_presence_speaking
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeCheckIfVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.DateTimeStyle
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.formatDateTime
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.SmallBorder
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.SpacedBy5dp
|
||||
@@ -80,11 +84,11 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
|
||||
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.ui.screen.loggedIn.home.CrossfadeCheckIfVideoIsOnline
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingRoomEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ParticipantTag
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.StatusTag
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.tags.StatusTag as MeetingSpaceStatusTag
|
||||
@@ -213,17 +217,10 @@ private fun ListenToRecordingButton(
|
||||
url: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val noAppMessage = stringRes(Res.string.nest_no_app_to_open_link)
|
||||
androidx.compose.material3.OutlinedButton(onClick = {
|
||||
val launched =
|
||||
runCatching {
|
||||
context.startActivity(
|
||||
android.content
|
||||
.Intent(android.content.Intent.ACTION_VIEW, url.toUri())
|
||||
.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
)
|
||||
}.isSuccess
|
||||
OutlinedButton(onClick = {
|
||||
val launched = runCatching { uriHandler.openUri(url) }.isSuccess
|
||||
if (!launched) {
|
||||
accountViewModel.toastManager.toast(
|
||||
Res.string.nest_chat_send_failed_title,
|
||||
@@ -564,13 +561,11 @@ fun MeetingSpacePlannedFlag(
|
||||
.padding(horizontal = 5.dp)
|
||||
},
|
||||
)
|
||||
val now = System.currentTimeMillis() / 1000L
|
||||
val now = TimeUtils.now()
|
||||
if (startsUnixSec != null && startsUnixSec > now) {
|
||||
val pretty =
|
||||
remember(startsUnixSec) {
|
||||
java.text.DateFormat
|
||||
.getDateTimeInstance(java.text.DateFormat.MEDIUM, java.text.DateFormat.SHORT)
|
||||
.format(java.util.Date(startsUnixSec * 1000L))
|
||||
formatDateTime(startsUnixSec * 1000L, DateTimeStyle.MEDIUM, DateTimeStyle.SHORT)
|
||||
}
|
||||
Text(
|
||||
text = stringRes(Res.string.meeting_space_planned_starts_at, pretty),
|
||||
|
||||
@@ -87,11 +87,11 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.grayText
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
||||
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.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageBanner
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageBannerBackground
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.experimental.music.playlist.MusicPlaylistEvent
|
||||
import com.vitorpamplona.quartz.experimental.music.track.MusicTrackEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -71,6 +71,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.grayText
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.GetVideoController
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.LoadThumbAndThenVideoView
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.PauseControllerWhenInBackground
|
||||
@@ -81,7 +82,6 @@ import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeaderBackground
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.experimental.music.track.MusicTrackEvent
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -45,7 +45,7 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.CrossfadeToDisplayComment
|
||||
import com.vitorpamplona.quartz.nip61Nutzaps.nutzap.NutzapEvent
|
||||
import com.vitorpamplona.quartz.nip61Nutzaps.nutzap.claimedSatsTotal
|
||||
import java.math.BigDecimal
|
||||
import com.vitorpamplona.quartz.utils.BigDecimal
|
||||
|
||||
/**
|
||||
* Renders a NIP-61 nutzap (kind 9321) as an activity card, like lightning zaps.
|
||||
|
||||
@@ -77,13 +77,13 @@ import com.vitorpamplona.amethyst.commons.ui.note.UserPicture
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size16Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size25dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor
|
||||
import com.vitorpamplona.amethyst.commons.util.PlatformNumberFormatter
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nipBCOnchainZaps.chain.BitcoinTx
|
||||
import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.NumberFormat
|
||||
|
||||
private const val MEMPOOL_TX_URL = "https://mempool.space/tx/"
|
||||
|
||||
@@ -314,7 +314,7 @@ private fun AmountRow(
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.Bottom, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
Text(
|
||||
text = NumberFormat.getNumberInstance().format(sats),
|
||||
text = PlatformNumberFormatter().format(sats),
|
||||
style = MaterialTheme.typography.displaySmall,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
color = orange,
|
||||
|
||||
+4
-28
@@ -42,6 +42,7 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.podcasts.fetchPreviewText
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.podcast_chapters
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
@@ -50,13 +51,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.grayText
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.quartz.podcasts.PodcastChapter
|
||||
import com.vitorpamplona.quartz.podcasts.PodcastChapters
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.coroutines.executeAsync
|
||||
|
||||
private sealed interface ChaptersUiState {
|
||||
data object Loading : ChaptersUiState
|
||||
@@ -79,8 +73,7 @@ fun PodcastChaptersSection(
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val state by produceState<ChaptersUiState>(ChaptersUiState.Loading, chaptersUrl) {
|
||||
val client = accountViewModel.httpClientBuilder.okHttpClientForPreview(chaptersUrl)
|
||||
val parsed = loadChapters(chaptersUrl, client)
|
||||
val parsed = accountViewModel.httpClientBuilder.fetchPreviewText(chaptersUrl)?.let { PodcastChapters.parse(it) }
|
||||
value = if (parsed != null) ChaptersUiState.Loaded(parsed.chapters) else ChaptersUiState.Failed
|
||||
}
|
||||
|
||||
@@ -133,30 +126,13 @@ fun PodcastChaptersSection(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadChapters(
|
||||
url: String,
|
||||
client: OkHttpClient,
|
||||
): PodcastChapters? =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val request = Request.Builder().url(url).build()
|
||||
client.newCall(request).executeAsync().use { response ->
|
||||
if (response.isSuccessful) PodcastChapters.parse(response.body.string()) else null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.w("PodcastChapters", "Failed to load chapters from $url", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatTimestamp(seconds: Long): String {
|
||||
val hours = seconds / 3600
|
||||
val minutes = (seconds % 3600) / 60
|
||||
val secs = seconds % 60
|
||||
return if (hours > 0) {
|
||||
"%d:%02d:%02d".format(hours, minutes, secs)
|
||||
"$hours:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}"
|
||||
} else {
|
||||
"%d:%02d".format(minutes, secs)
|
||||
"$minutes:${secs.toString().padStart(2, '0')}"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -43,7 +43,7 @@ import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.podcasts.PodcastRemoteContent
|
||||
import com.vitorpamplona.amethyst.commons.podcasts.fetchPreviewText
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.podcast_chapters_count
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.PodcastSoundbites
|
||||
@@ -67,8 +67,7 @@ fun PodcastChaptersView(
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val chapters by produceState(initialValue = emptyList<PodcastChapter>(), chaptersUrl) {
|
||||
val client = accountViewModel.httpClientBuilder.okHttpClientForPreview(chaptersUrl)
|
||||
val body = PodcastRemoteContent.fetchText(chaptersUrl, client)
|
||||
val body = accountViewModel.httpClientBuilder.fetchPreviewText(chaptersUrl)
|
||||
value = body?.let { PodcastChapters.parse(it)?.chapters }?.filter { it.title?.isNotBlank() == true } ?: emptyList()
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -29,12 +29,12 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.PodcastSoundbites
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.GetVideoController
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.PauseControllerWhenInBackground
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.wavefront.syntheticWaveformFor
|
||||
import com.vitorpamplona.quartz.podcasts.PodcastAudio
|
||||
|
||||
+2
-3
@@ -40,7 +40,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.podcasts.PodcastRemoteContent
|
||||
import com.vitorpamplona.amethyst.commons.podcasts.fetchPreviewText
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.podcast_transcript
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
@@ -58,8 +58,7 @@ fun PodcastTranscriptView(
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val transcript by produceState(initialValue = null as String?, transcriptUrl) {
|
||||
val client = accountViewModel.httpClientBuilder.okHttpClientForPreview(transcriptUrl)
|
||||
val body = PodcastRemoteContent.fetchText(transcriptUrl, client)
|
||||
val body = accountViewModel.httpClientBuilder.fetchPreviewText(transcriptUrl)
|
||||
value = body?.let { cleanTranscript(it) }?.takeIf { it.isNotBlank() }
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.types
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
@@ -100,10 +99,10 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.subtleBorder
|
||||
import com.vitorpamplona.amethyst.commons.util.showCount
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
|
||||
import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent
|
||||
import com.vitorpamplona.quartz.nip88Polls.poll.tags.PollType
|
||||
@@ -769,7 +768,7 @@ fun RenderPollManualLongPreview() {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StateFlowValueCalledInComposition")
|
||||
@Suppress("StateFlowValueCalledInComposition")
|
||||
@Preview
|
||||
@Composable
|
||||
fun RenderPollGameResultsPreview() {
|
||||
@@ -845,7 +844,7 @@ fun RenderPollGameResultsPreview() {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StateFlowValueCalledInComposition")
|
||||
@Suppress("StateFlowValueCalledInComposition")
|
||||
@Preview
|
||||
@Composable
|
||||
fun RenderPollColorResultsPreview() {
|
||||
@@ -954,7 +953,7 @@ fun RenderPollColorResultsPreview() {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StateFlowValueCalledInComposition")
|
||||
@Suppress("StateFlowValueCalledInComposition")
|
||||
@Preview
|
||||
@Composable
|
||||
fun RenderPollMultiChoicePreview() {
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -31,9 +31,8 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.withFrameMillis
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.FilterQuality
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.core.graphics.createBitmap
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.service.image.argbPixelsToImageBitmap
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.Ps1SaveCard
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size24dp
|
||||
import com.vitorpamplona.quartz.experimental.ps1saves.Ps1SaveEvent
|
||||
@@ -92,9 +91,7 @@ private fun Ps1SaveIconImage(icon: Ps1SaveIcon) {
|
||||
val frames =
|
||||
remember(icon) {
|
||||
icon.frames.map { pixels ->
|
||||
createBitmap(Ps1SaveIcon.SIZE, Ps1SaveIcon.SIZE)
|
||||
.apply { setPixels(pixels, 0, Ps1SaveIcon.SIZE, 0, 0, Ps1SaveIcon.SIZE, Ps1SaveIcon.SIZE) }
|
||||
.asImageBitmap()
|
||||
argbPixelsToImageBitmap(pixels, Ps1SaveIcon.SIZE, Ps1SaveIcon.SIZE)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -56,12 +56,12 @@ import com.vitorpamplona.amethyst.commons.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.LocalRichTextPlatform
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp
|
||||
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.components.markdown.RenderContentAsMarkdown
|
||||
import com.vitorpamplona.quartz.experimental.publications.PublicationContentEvent
|
||||
import com.vitorpamplona.quartz.experimental.publications.PublicationIndexEvent
|
||||
import com.vitorpamplona.quartz.experimental.publications.PublicationSectionRef
|
||||
@@ -129,7 +129,7 @@ fun RenderPublicationSection(
|
||||
if (markdown.isNotBlank()) {
|
||||
val tags = remember(noteEvent) { noteEvent.tags.toImmutableListOfLists() }
|
||||
|
||||
RenderContentAsMarkdown(
|
||||
LocalRichTextPlatform.current.Markdown(
|
||||
content = markdown,
|
||||
tags = tags,
|
||||
canPreview = canPreview && !makeItShort,
|
||||
|
||||
@@ -88,6 +88,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.grayText
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.subtleBorder
|
||||
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
|
||||
@@ -758,9 +759,9 @@ fun RenderSoftwareAsset(
|
||||
internal fun formatBytes(bytes: Long): String {
|
||||
if (bytes < 1024L) return "$bytes B"
|
||||
val kb = bytes / 1024.0
|
||||
if (kb < 1024) return "%.1f KB".format(kb)
|
||||
if (kb < 1024) return "${DecimalPatternFormatter("0.0").format(kb)} KB"
|
||||
val mb = kb / 1024
|
||||
if (mb < 1024) return "%.1f MB".format(mb)
|
||||
if (mb < 1024) return "${DecimalPatternFormatter("0.0").format(mb)} MB"
|
||||
val gb = mb / 1024
|
||||
return "%.2f GB".format(gb)
|
||||
return "${DecimalPatternFormatter("0.00").format(gb)} GB"
|
||||
}
|
||||
|
||||
@@ -59,13 +59,13 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdPadding
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.blackTagModifier
|
||||
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.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.note.GalleryUnloaded
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeaderBackground
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip51FollowSets.FollowSetCard
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.types
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -41,13 +40,12 @@ 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.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
@@ -63,9 +61,9 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.util.countToHumanReadableBytes
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.components.ShowMoreButton
|
||||
import com.vitorpamplona.amethyst.ui.note.getGradient
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
|
||||
import com.vitorpamplona.quartz.nip35Torrents.tags.FileTag
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -218,16 +216,14 @@ fun DisplayFileList(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
||||
val context = LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
try {
|
||||
val linkStr = link()
|
||||
if (linkStr != null) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, linkStr.toUri())
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
context.startActivity(intent)
|
||||
uriHandler.openUri(linkStr)
|
||||
} else {
|
||||
accountViewModel.toastManager.toast(Res.string.torrent_failure, Res.string.torrent_no_info)
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
||||
import com.vitorpamplona.amethyst.commons.util.countToHumanReadableBytes
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -45,6 +45,7 @@ import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.compose.ContentFrame
|
||||
import androidx.media3.ui.compose.SURFACE_TYPE_TEXTURE_VIEW
|
||||
import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
@@ -64,7 +65,6 @@ import com.vitorpamplona.amethyst.service.playback.composable.DEFAULT_MUTED_SETT
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.GetVideoController
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.PauseControllerWhenInBackground
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.AnimatedSaveButton
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.AnimatedShareButton
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.MuteButton
|
||||
|
||||
@@ -61,9 +61,9 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.CrossfadeToDisplayComment
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
+2
-6
@@ -31,7 +31,7 @@ import com.vitorpamplona.amethyst.commons.marmot.MarmotGroupIconUpload
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.failed_to_save_the_video
|
||||
import com.vitorpamplona.amethyst.commons.resources.video_saved_to_the_gallery
|
||||
import com.vitorpamplona.amethyst.commons.service.OnlineChecker
|
||||
import com.vitorpamplona.amethyst.commons.service.isVideoOnline
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.UrlPreviewState
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
@@ -40,7 +40,6 @@ import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send.MarmotGroupIconUploader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
// Account actions that need Android (a content Uri, the media store) or the JVM HTTP stack the
|
||||
// app's OkHttp clients come from. They stay out of AccountViewModel so the view model needs neither.
|
||||
@@ -54,10 +53,7 @@ fun AccountViewModel.urlPreview(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun AccountViewModel.checkVideoIsOnline(videoUrl: String): Boolean =
|
||||
withContext(Dispatchers.IO) {
|
||||
OnlineChecker.isOnline(videoUrl, httpClientBuilder::okHttpClientForVideo)
|
||||
}
|
||||
suspend fun AccountViewModel.checkVideoIsOnline(videoUrl: String): Boolean = httpClientBuilder.isVideoOnline(videoUrl)
|
||||
|
||||
/**
|
||||
* Encrypt + upload a picked image as a group avatar (canonical `marmot-group-image-v1` scheme).
|
||||
|
||||
+1
-1
@@ -69,6 +69,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.chats.ui.AutoScrollToNewest
|
||||
import com.vitorpamplona.amethyst.commons.cordn.CordnGroupManager
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
@@ -113,7 +114,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.model.cordn.CordnMediaService
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
||||
import com.vitorpamplona.amethyst.service.uploads.MetadataStripper
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.RecordingResult
|
||||
|
||||
+2
-3
@@ -28,7 +28,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
@@ -40,7 +40,6 @@ import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||
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.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderAudioWithWaveform
|
||||
@@ -64,7 +63,7 @@ fun RenderEncryptedFile(
|
||||
val mimeType = noteEvent.mimeType()
|
||||
|
||||
if (algo == AESGCM.NAME && key != null && nonce != null) {
|
||||
Amethyst.instance.keyCache.add(noteEvent.content, AESGCM(key, nonce), mimeType)
|
||||
accountViewModel.account.encryptionKeyCache.add(noteEvent.content, AESGCM(key, nonce), mimeType)
|
||||
|
||||
// Audio is neither an image nor a video, and the image-or-video split
|
||||
// below would hand it to the video player: a picture-less surface where
|
||||
|
||||
+1
-1
@@ -83,6 +83,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.util.showCount
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||
@@ -90,7 +91,6 @@ import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.ShowUserSuggestionList
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.RoomChatFileUploadDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.DisplayReplyingToNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SettingsCategoryFirstModifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory
|
||||
|
||||
@Composable
|
||||
|
||||
+6
-23
@@ -20,21 +20,20 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.quick_action_copy_note_id
|
||||
import com.vitorpamplona.amethyst.commons.resources.quick_action_share_browser_link
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.rememberTextSharer
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier
|
||||
@@ -49,7 +48,8 @@ fun LinkChatButton(
|
||||
) {
|
||||
val quickActionShareBrowserLinkStr = stringRes(Res.string.quick_action_share_browser_link)
|
||||
val quickActionCopyNoteIdStr = stringRes(Res.string.quick_action_copy_note_id)
|
||||
val context = LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val sharer = rememberTextSharer()
|
||||
|
||||
FilledTonalButton(
|
||||
modifier =
|
||||
@@ -57,26 +57,9 @@ fun LinkChatButton(
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = {
|
||||
val intent = Intent(Intent.ACTION_VIEW, channel.toNostrUri().toUri())
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
runCatching { uriHandler.openUri(channel.toNostrUri()) }
|
||||
|
||||
context.startActivity(intent)
|
||||
|
||||
val sendIntent =
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TEXT, channel.toNostrUri())
|
||||
putExtra(Intent.EXTRA_TITLE, quickActionShareBrowserLinkStr)
|
||||
}
|
||||
|
||||
val shareIntent =
|
||||
Intent.createChooser(
|
||||
sendIntent,
|
||||
quickActionCopyNoteIdStr,
|
||||
)
|
||||
|
||||
context.startActivity(shareIntent)
|
||||
sharer.share(channel.toNostrUri(), quickActionShareBrowserLinkStr, quickActionCopyNoteIdStr)
|
||||
},
|
||||
contentPadding = ZeroPadding,
|
||||
) {
|
||||
|
||||
+3
-8
@@ -20,15 +20,13 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||
@@ -46,7 +44,7 @@ fun OpenChatButton(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
FilledTonalButton(
|
||||
modifier =
|
||||
@@ -54,10 +52,7 @@ fun OpenChatButton(
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = {
|
||||
val intent = Intent(Intent.ACTION_VIEW, channel.toNostrUri().toUri())
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
|
||||
context.startActivity(intent)
|
||||
runCatching { uriHandler.openUri(channel.toNostrUri()) }
|
||||
},
|
||||
contentPadding = ZeroPadding,
|
||||
) {
|
||||
|
||||
+3
-18
@@ -20,13 +20,11 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
@@ -34,6 +32,7 @@ import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChann
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.quick_action_share
|
||||
import com.vitorpamplona.amethyst.commons.resources.quick_action_share_browser_link
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.rememberTextSharer
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier
|
||||
@@ -49,7 +48,7 @@ fun ShareChatButton(
|
||||
) {
|
||||
val quickActionShareBrowserLinkStr = stringRes(Res.string.quick_action_share_browser_link)
|
||||
val quickActionShareStr = stringRes(Res.string.quick_action_share)
|
||||
val context = LocalContext.current
|
||||
val sharer = rememberTextSharer()
|
||||
|
||||
FilledTonalButton(
|
||||
modifier =
|
||||
@@ -57,21 +56,7 @@ fun ShareChatButton(
|
||||
.padding(horizontal = 3.dp)
|
||||
.width(50.dp),
|
||||
onClick = {
|
||||
val sendIntent =
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
type = "text/plain"
|
||||
putExtra(Intent.EXTRA_TEXT, njumpLink(channel.toNEvent()))
|
||||
putExtra(Intent.EXTRA_TITLE, quickActionShareBrowserLinkStr)
|
||||
}
|
||||
|
||||
val shareIntent =
|
||||
Intent.createChooser(
|
||||
sendIntent,
|
||||
quickActionShareStr,
|
||||
)
|
||||
|
||||
context.startActivity(shareIntent)
|
||||
sharer.share(njumpLink(channel.toNEvent()), quickActionShareBrowserLinkStr, quickActionShareStr)
|
||||
},
|
||||
contentPadding = ZeroPadding,
|
||||
) {
|
||||
|
||||
+1
-1
@@ -76,8 +76,8 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SettingsCategorySpacingModifi
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField
|
||||
|
||||
+1
-1
@@ -29,12 +29,12 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeCheckIfVideoIsOnline
|
||||
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.screen.loggedIn.home.CrossfadeCheckIfVideoIsOnline
|
||||
|
||||
@Composable
|
||||
fun ShowVideoStreaming(
|
||||
|
||||
+1
-1
@@ -41,6 +41,7 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.commons.nip53LiveActivities.ui.LiveFlag
|
||||
import com.vitorpamplona.amethyst.commons.nip53LiveActivities.ui.OfflineFlag
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CheckIfVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.UserPicture
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.RowColSpacing
|
||||
@@ -51,7 +52,6 @@ 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.screen.loggedIn.home.CheckIfVideoIsOnline
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
|
||||
@Composable
|
||||
|
||||
+1
-1
@@ -55,13 +55,13 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdPadding
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
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.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
+1
-1
@@ -57,6 +57,7 @@ import com.vitorpamplona.amethyst.commons.nip53LiveActivities.ui.LiveFlag
|
||||
import com.vitorpamplona.amethyst.commons.nip53LiveActivities.ui.OfflineFlag
|
||||
import com.vitorpamplona.amethyst.commons.nip53LiveActivities.ui.ScheduledFlag
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteAndMap
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CheckIfVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LoadLiveActivityChannel
|
||||
@@ -67,7 +68,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner
|
||||
import com.vitorpamplona.amethyst.ui.note.Gallery
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header.LiveActivitiesChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CheckIfVideoIsOnline
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveStreamLike
|
||||
|
||||
+1
-1
@@ -50,8 +50,8 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.commons.util.showAmountInteger
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.tags.PriceTag
|
||||
|
||||
|
||||
+1
-1
@@ -91,9 +91,9 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.SquaredQuoteBorderModifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation
|
||||
import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.tags.ConditionTag
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.AddInboxRelayCard
|
||||
|
||||
@Preview
|
||||
|
||||
-49
@@ -52,7 +52,6 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
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.ui.Alignment
|
||||
@@ -77,8 +76,6 @@ import com.vitorpamplona.amethyst.commons.resources.feed_is_empty
|
||||
import com.vitorpamplona.amethyst.commons.resources.home_tab_everything
|
||||
import com.vitorpamplona.amethyst.commons.resources.new_threads
|
||||
import com.vitorpamplona.amethyst.commons.resources.refresh
|
||||
import com.vitorpamplona.amethyst.commons.service.OnlineChecker
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedError
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.LoadingFeed
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.PagerStateKeys
|
||||
@@ -105,7 +102,6 @@ import com.vitorpamplona.amethyst.ui.feeds.SaveableFeedContentState
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.backups.BackupConflictCards
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.checkVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.NewGeoPostButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.NewHashtagPostButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssemblerSubscription
|
||||
@@ -594,51 +590,6 @@ fun HomeFeedEmpty(onRefresh: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CheckIfVideoIsOnline(
|
||||
url: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
whenOnline: @Composable (Boolean) -> Unit,
|
||||
) {
|
||||
val online by produceState(
|
||||
initialValue = OnlineChecker.isOnlineCached(url),
|
||||
key1 = url,
|
||||
) {
|
||||
val isOnline = accountViewModel.checkVideoIsOnline(url)
|
||||
if (value != isOnline) {
|
||||
value = isOnline
|
||||
}
|
||||
}
|
||||
|
||||
whenOnline(online)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CrossfadeCheckIfVideoIsOnline(
|
||||
url: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
whenOnline: @Composable () -> Unit,
|
||||
) {
|
||||
val online by produceState(
|
||||
initialValue = OnlineChecker.isOnlineCached(url),
|
||||
key1 = url,
|
||||
) {
|
||||
val isOnline = accountViewModel.checkVideoIsOnline(url)
|
||||
if (value != isOnline) {
|
||||
value = isOnline
|
||||
}
|
||||
}
|
||||
|
||||
CrossfadeIfEnabled(
|
||||
targetState = online,
|
||||
label = "CheckIfUrlIsOnline",
|
||||
) {
|
||||
if (it) {
|
||||
whenOnline()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WatchAccountForHomeScreen(
|
||||
liveFeedState: ChannelFeedContentState,
|
||||
|
||||
+1
-1
@@ -138,9 +138,9 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackBu
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.authenticate
|
||||
import com.vitorpamplona.amethyst.ui.note.rememberAuthPromptLabels
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNsec
|
||||
|
||||
+1
-1
@@ -103,11 +103,11 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdPadding
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.TabRowHeight
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.externalLinkForNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.DrawUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.PeopleListView
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.RenderAddUserFieldAndSuggestions
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
+1
-1
@@ -91,11 +91,11 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdPadding
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.note.externalLinkForNote
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.DrawUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.PeopleListView
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.RenderAddUserFieldAndSuggestions
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user