mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
Merge branch 'main' into claude/kind-keller-itzy63
Main's97136c22moved DisappearingScaffold, SafeImeInsets, the emoji text and the user-name text into commonsUI, and turned CrossfadeIfEnabled's `accountViewModel` parameter into a LocalAnimationsEnabled composition local. Cordn's coordinators screen was the last caller still naming the old package and passing the removed argument; every other cordn screen already sits on the app-level DisappearingScaffold wrapper, which main kept. Both conflicts were in the calendar ViewModel tests, where this branch had mitigated a cross-test Main-dispatcher leak by yielding on Dispatchers.Default before resetMain. Main'sbc44d870fixes the same leak deterministically by joining the model scopes under a timeout, so those two files take main's version whole. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012BfD4txdnsaPRXmNXbup9n
This commit is contained in:
@@ -28,6 +28,8 @@ TASKS=(
|
||||
:quartz:jvmTest
|
||||
:commons:jvmTest
|
||||
:commonsUI:jvmTest
|
||||
# Android-only commonsUI code (e.g. SafeImeInsets) is tested here, not in jvmTest.
|
||||
:commonsUI:testAndroidHostTest
|
||||
:nestsClient:jvmTest
|
||||
:quic:jvmTest
|
||||
:marmotQuic:jvmTest
|
||||
|
||||
@@ -379,6 +379,7 @@ jobs:
|
||||
:quartz:jvmTest \
|
||||
:commons:jvmTest \
|
||||
:commonsUI:jvmTest \
|
||||
:commonsUI:testAndroidHostTest \
|
||||
:nestsClient:jvmTest \
|
||||
:amethyst:testFdroidDebugUnitTest \
|
||||
:amethyst:testPlayDebugUnitTest \
|
||||
|
||||
+2
-2
@@ -61,11 +61,11 @@ import org.junit.Test
|
||||
* [aCancelledImeAnimationMustNotWedgeTheAnimatedInset] FAILS on every version from 1.4.0 on, so it
|
||||
* is [Ignore]d to keep CI green. It is not a test of Amethyst code — it is the upstream repro we
|
||||
* attached to the bug. **Re-run it by hand after every Compose upgrade**: when it passes, the
|
||||
* upstream fix has landed and [com.vitorpamplona.amethyst.ui.insets.SafeImeInsets] can be retired.
|
||||
* upstream fix has landed and [com.vitorpamplona.amethyst.commons.ui.insets.SafeImeInsets] can be retired.
|
||||
*
|
||||
* [theAnimationTargetSurvivesTheWedge] documents the asymmetry that makes a workaround possible
|
||||
* and is expected to PASS — `updateImeAnimationTarget` is called outside the guard. It stays
|
||||
* enabled, because it guards the premise [com.vitorpamplona.amethyst.ui.insets.SafeImeInsets]
|
||||
* enabled, because it guards the premise [com.vitorpamplona.amethyst.commons.ui.insets.SafeImeInsets]
|
||||
* depends on: if a future Compose release stopped keeping `imeAnimationTarget` current, our
|
||||
* fallback would silently start reading a dead value too.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
|
||||
/**
|
||||
* The account a signer request acts as — avatar + display name — so it's clear WHICH logged-in
|
||||
|
||||
+1
-1
@@ -95,11 +95,11 @@ import com.vitorpamplona.amethyst.commons.resources.nip46_signer_batch_select_no
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip46_signer_batch_signing_as
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip46_signer_batch_title
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip46_signer_messages_with
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.service.call.CallSessionBridge
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
@@ -2076,7 +2076,7 @@ class Account(
|
||||
// blessed status. 40002 survives only as a read-compat tail from the
|
||||
// 10002 -> 40001 -> 40002 migration, so we were the last active writer of a kind
|
||||
// their clients no longer thread on. Reading 40002 stays supported (see
|
||||
// [com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.isMinichatReply]).
|
||||
// [com.vitorpamplona.amethyst.commons.model.chats.isMinichatReply]).
|
||||
//
|
||||
// Attached media rides as URLs appended to the content.
|
||||
val root = rootEvent.tags.buzzThreadRoot() ?: rootEvent.tags.buzzThreadReply() ?: rootEvent.id
|
||||
|
||||
@@ -70,8 +70,8 @@ import com.vitorpamplona.amethyst.commons.resources.napplet_consent_deny_always
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_consent_hide_event
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_consent_not_now
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_consent_show_event
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -45,6 +45,7 @@ import androidx.media3.common.Player
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.compose.ContentFrame
|
||||
import androidx.media3.ui.compose.SURFACE_TYPE_TEXTURE_VIEW
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.getDialogWindow
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.BottomGradientOverlay
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.FullscreenSwipeControlsState
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.FullscreenSwipeLevelIndicator
|
||||
@@ -62,7 +63,6 @@ import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.LoadedMe
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.isHlsMedia
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.wavefront.AudioPlayingAnimation
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.wavefront.rememberIsAudioTrack
|
||||
import com.vitorpamplona.amethyst.ui.components.getDialogWindow
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
internal const val SKIP_SECONDS = 10
|
||||
|
||||
+1
-1
@@ -97,13 +97,13 @@ import com.vitorpamplona.amethyst.commons.resources.relay_auth_why_read_venue
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_auth_why_send_dm
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_auth_why_thread
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_auth_why_thread_with
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.RelayIconFilter
|
||||
import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
|
||||
+44
-357
@@ -22,465 +22,152 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.NoteState
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.textNoteModifications
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.isMinichatReply
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.isForCommunity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeCommunityApprovalNeedStatus as sharedObserveCommunityApprovalNeedStatus
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNote as sharedObserveNote
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteAndMap as sharedObserveNoteAndMap
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteEvent as sharedObserveNoteEvent
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteEventAndMap as sharedObserveNoteEventAndMap
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteEventAndMapNotNull as sharedObserveNoteEventAndMapNotNull
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteHasEvent as sharedObserveNoteHasEvent
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteMinichatReplyCount as sharedObserveNoteMinichatReplyCount
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteModifications as sharedObserveNoteModifications
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteOts as sharedObserveNoteOts
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteReactionCount as sharedObserveNoteReactionCount
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteReactions as sharedObserveNoteReactions
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteReferences as sharedObserveNoteReferences
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteReplies as sharedObserveNoteReplies
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteReplyCount as sharedObserveNoteReplyCount
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteRepostCount as sharedObserveNoteRepostCount
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteReposts as sharedObserveNoteReposts
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteRepostsBy as sharedObserveNoteRepostsBy
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.observeNoteZaps as sharedObserveNoteZaps
|
||||
|
||||
/*
|
||||
* Android overloads of the shared per-note observers in commons `relayClient/event`: they take the
|
||||
* account and event-finder data source from [AccountViewModel] instead of the composition locals,
|
||||
* so they also work under Activity roots that don't provide those locals.
|
||||
*/
|
||||
|
||||
@Composable
|
||||
fun observeNote(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
): State<NoteState> = sharedObserveNote(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow = remember(note) { note.flow().metadata.stateFlow }
|
||||
return flow.collectAsStateWithLifecycle()
|
||||
}
|
||||
|
||||
/**
|
||||
* [observeNote] without the relay half: watches LocalCache and asks no relay for the note.
|
||||
*
|
||||
* For a NIP-17 rumor, which has no fetchable id — putting one in a REQ would tell relays the
|
||||
* private event's identity, the leak [com.vitorpamplona.amethyst.commons.model.Note.isPrivateRumor]
|
||||
* guards everywhere else. Nothing is lost by not asking: a rumor only ever reaches the cache by
|
||||
* unwrapping the envelope that carried it, and the always-on gift-wrap tail re-fetches a week of
|
||||
* those on every cold start, so this flow fires on its own once the envelope lands.
|
||||
*/
|
||||
@Composable
|
||||
fun observeNoteLocally(note: Note): State<NoteState> {
|
||||
val flow = remember(note) { note.flow().metadata.stateFlow }
|
||||
return flow.collectAsStateWithLifecycle()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
inline fun <reified T : Event> observeNoteEvent(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<T?> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
): State<T?> = sharedObserveNoteEvent<T>(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(note) {
|
||||
note
|
||||
.flow()
|
||||
.metadata.stateFlow
|
||||
.mapLatest { it.note.event as? T? }
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(note.event as? T?)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun <T> observeNoteAndMap(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
map: (Note) -> T,
|
||||
): State<T> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
): State<T> = sharedObserveNoteAndMap<T>(note, accountViewModel.account, accountViewModel.dataSources().eventFinder, map)
|
||||
|
||||
val flow =
|
||||
remember(note) {
|
||||
note
|
||||
.flow()
|
||||
.metadata.stateFlow
|
||||
.mapLatest { map(it.note) }
|
||||
.distinctUntilChanged()
|
||||
.flowOn(Dispatchers.IO)
|
||||
}
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
return flow.collectAsStateWithLifecycle(map(note))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@Composable
|
||||
fun <T, U> observeNoteEventAndMapNotNull(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
map: (T) -> U,
|
||||
): State<U?> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
): State<U?> = sharedObserveNoteEventAndMapNotNull<T, U>(note, accountViewModel.account, accountViewModel.dataSources().eventFinder, map)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(note) {
|
||||
note
|
||||
.flow()
|
||||
.metadata.stateFlow
|
||||
.mapLatest { (it.note.event as? T)?.let { map(it) } }
|
||||
.distinctUntilChanged()
|
||||
.flowOn(Dispatchers.IO)
|
||||
}
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
return flow.collectAsStateWithLifecycle((note.event as? T)?.let { map(it) })
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@Composable
|
||||
fun <T, U> observeNoteEventAndMap(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
map: (T?) -> U,
|
||||
): State<U> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
): State<U> = sharedObserveNoteEventAndMap<T, U>(note, accountViewModel.account, accountViewModel.dataSources().eventFinder, map)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(note) {
|
||||
note
|
||||
.flow()
|
||||
.metadata.stateFlow
|
||||
.mapLatest { map(it.note.event as? T) }
|
||||
.distinctUntilChanged()
|
||||
.flowOn(Dispatchers.IO)
|
||||
}
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
return flow.collectAsStateWithLifecycle(map(note.event as? T))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeNoteHasEvent(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Boolean> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(note) {
|
||||
note
|
||||
.flow()
|
||||
.metadata.stateFlow
|
||||
.mapLatest { it.note.event != null }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(note.event != null)
|
||||
}
|
||||
): State<Boolean> = sharedObserveNoteHasEvent(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
@Composable
|
||||
fun observeNoteReplies(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState?> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
): State<NoteState?> = sharedObserveNoteReplies(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow = remember(note) { note.flow().replies.stateFlow }
|
||||
return flow.collectAsStateWithLifecycle()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeNoteReplyCount(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Int> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
): State<Int> = sharedObserveNoteReplyCount(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(note) {
|
||||
note
|
||||
.flow()
|
||||
.replies.stateFlow
|
||||
.sample(200)
|
||||
.mapLatest { it.note.replies.size }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(note.replies.size)
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of a chat message's **minichat** replies — its kind-1111 [CommentEvent]
|
||||
* children only (inline quote-replies are ordinary kind-9/42 messages and are not
|
||||
* counted here). Drives the "N replies" chip that opens the minichat.
|
||||
*
|
||||
* Mounting this registers the message with [EventFinderFilterAssemblerSubscription], which
|
||||
* batches the visible messages' ids into shared REQs for their replies (kind-1111 among
|
||||
* them) — so for public chats (NIP-28/NIP-29) the thread replies load, and the chip appears,
|
||||
* just by rendering the rows. Concord's kind-1111 replies instead arrive over the channel
|
||||
* plane, so that REQ finds nothing there and is a harmless no-op.
|
||||
*/
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeNoteMinichatReplyCount(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Int> {
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
|
||||
val flow =
|
||||
remember(note) {
|
||||
note
|
||||
.flow()
|
||||
.replies.stateFlow
|
||||
.sample(200)
|
||||
.mapLatest { it.note.replies.count { reply -> isMinichatReply(reply.event) } }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(note.replies.count { isMinichatReply(it.event) })
|
||||
}
|
||||
): State<Int> = sharedObserveNoteMinichatReplyCount(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
@Composable
|
||||
fun observeNoteReactions(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState?> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
): State<NoteState?> = sharedObserveNoteReactions(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow = remember(note) { note.flow().reactions.stateFlow }
|
||||
return flow.collectAsStateWithLifecycle()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeNoteReactionCount(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Int> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(note) {
|
||||
note
|
||||
.flow()
|
||||
.reactions.stateFlow
|
||||
.sample(200)
|
||||
.mapLatest { it.note.countReactions() }
|
||||
.distinctUntilChanged()
|
||||
.flowOn(Dispatchers.IO)
|
||||
}
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
return flow.collectAsStateWithLifecycle(note.countReactions())
|
||||
}
|
||||
): State<Int> = sharedObserveNoteReactionCount(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
@Composable
|
||||
fun observeNoteZaps(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState?> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow = remember(note) { note.flow().zaps.stateFlow }
|
||||
return flow.collectAsStateWithLifecycle()
|
||||
}
|
||||
): State<NoteState?> = sharedObserveNoteZaps(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
@Composable
|
||||
fun observeNoteReposts(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState?> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
): State<NoteState?> = sharedObserveNoteReposts(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow = remember(note) { note.flow().boosts.stateFlow }
|
||||
return flow.collectAsStateWithLifecycle()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeNoteRepostsBy(
|
||||
note: Note,
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Boolean> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
): State<Boolean> = sharedObserveNoteRepostsBy(note, user, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(note) {
|
||||
note
|
||||
.flow()
|
||||
.boosts.stateFlow
|
||||
.mapLatest { it.note.isBoostedBy(user) }
|
||||
.distinctUntilChanged()
|
||||
.flowOn(Dispatchers.IO)
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(note.isBoostedBy(user))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeNoteRepostCount(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Int> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(note) {
|
||||
note
|
||||
.flow()
|
||||
.boosts.stateFlow
|
||||
.sample(200)
|
||||
.mapLatest { note.boosts.size }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(note.boosts.size)
|
||||
}
|
||||
): State<Int> = sharedObserveNoteRepostCount(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
@Composable
|
||||
fun observeNoteReferences(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Boolean> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device.
|
||||
// On-chain zaps piggyback `note.flow().zaps.stateFlow` because Note.addOnchainZap
|
||||
// invalidates the same `flowSet.zaps`. If that ever moves to a dedicated flow,
|
||||
// add it here too — otherwise on-chain-only notes will stop pinging the chevron.
|
||||
val flow =
|
||||
remember(note) {
|
||||
combine(
|
||||
note.flow().zaps.stateFlow,
|
||||
note.flow().boosts.stateFlow,
|
||||
note.flow().reactions.stateFlow,
|
||||
) { zapState, _, _ ->
|
||||
zapState.note.hasZapsBoostsOrReactions()
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(note.hasZapsBoostsOrReactions())
|
||||
}
|
||||
): State<Boolean> = sharedObserveNoteReferences(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
@Composable
|
||||
fun observeNoteOts(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<NoteState?> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
): State<NoteState?> = sharedObserveNoteOts(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow = remember(note) { note.flow().ots.stateFlow }
|
||||
return flow.collectAsStateWithLifecycle()
|
||||
}
|
||||
|
||||
// Resolves the actual modification list off the main thread and filters identical results,
|
||||
// so the caller's LaunchedEffect only fires when the list of edits truly changes.
|
||||
// `sample(500)` collapses bursts — a heavily-edited note can emit hundreds of times during
|
||||
// initial relay sync, and we only need the last state per ~half second.
|
||||
// Returns `null` until the first IO resolution completes — callers should treat that as
|
||||
// "still loading" and not flip their UI to "no edits".
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
fun observeNoteModifications(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<List<Note>?> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
|
||||
return produceState<List<Note>?>(initialValue = null, note) {
|
||||
note
|
||||
.flow()
|
||||
.edits
|
||||
.stateFlow
|
||||
.sample(500)
|
||||
.mapLatest { note.textNoteModifications() }
|
||||
.distinctUntilChanged()
|
||||
.flowOn(Dispatchers.IO)
|
||||
.collect { value = it }
|
||||
}
|
||||
}
|
||||
): State<List<Note>?> = sharedObserveNoteModifications(note, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
@Composable
|
||||
fun observeCommunityApprovalNeedStatus(
|
||||
note: Note,
|
||||
community: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<Boolean?> {
|
||||
// Subscribe in the relay for changes in this note.
|
||||
EventFinderFilterAssemblerSubscription(note, accountViewModel)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(note, community) {
|
||||
combine(
|
||||
community.flow().metadata.stateFlow,
|
||||
note.flow().boosts.stateFlow,
|
||||
) { communityMetadata, boosts ->
|
||||
(communityMetadata.note.event as? CommunityDefinitionEvent)?.let { communityDefEvent ->
|
||||
val moderators = communityDefEvent.moderatorKeys().toSet()
|
||||
|
||||
if (note.author?.pubkeyHex in moderators) {
|
||||
false
|
||||
} else {
|
||||
val isModerator = accountViewModel.account.userProfile().pubkeyHex in moderators
|
||||
|
||||
if (isModerator) {
|
||||
val wasAlreadyApproved =
|
||||
note.boosts.any {
|
||||
val approvalEvent = it.event
|
||||
(approvalEvent is CommunityPostApprovalEvent || approvalEvent is RepostEvent || approvalEvent is GenericRepostEvent) &&
|
||||
approvalEvent.pubKey in moderators &&
|
||||
approvalEvent.isForCommunity(community.idHex)
|
||||
}
|
||||
!wasAlreadyApproved
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}.distinctUntilChanged()
|
||||
.flowOn(Dispatchers.IO)
|
||||
}
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
return flow.collectAsStateWithLifecycle(false)
|
||||
}
|
||||
): State<Boolean?> = sharedObserveCommunityApprovalNeedStatus(note, community, accountViewModel.account, accountViewModel.dataSources().eventFinder)
|
||||
|
||||
+2
-19
@@ -26,39 +26,22 @@ import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderFilterAssemblerSubscription as SharedUserFinderSubscription
|
||||
|
||||
@SuppressLint("StateFlowValueCalledInComposition")
|
||||
@Composable
|
||||
fun UserFinderFilterAssemblerSubscription(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
) = UserFinderFilterAssemblerSubscription(
|
||||
) = SharedUserFinderSubscription(
|
||||
user,
|
||||
accountViewModel.account,
|
||||
accountViewModel.dataSources().userFinder,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun UserFinderFilterAssemblerSubscription(
|
||||
user: User,
|
||||
forAccount: Account,
|
||||
dataSource: UserFinderFilterAssembler,
|
||||
) {
|
||||
// different screens get different states
|
||||
// even if they are tracking the same tag.
|
||||
val state =
|
||||
remember(user) {
|
||||
UserFinderQueryState(user, forAccount)
|
||||
}
|
||||
|
||||
LifecycleAwareKeyDataSourceSubscription(state, dataSource)
|
||||
}
|
||||
|
||||
/**
|
||||
* Watches all the users that are cited in the parent post.
|
||||
* So that we load their relay lists and find posts
|
||||
|
||||
+8
-88
@@ -47,10 +47,13 @@ import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.observeUserAboutMe as sharedObserveUserAboutMe
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.observeUserBanner as sharedObserveUserBanner
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.observeUserInfo as sharedObserveUserInfo
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.observeUserPicture as sharedObserveUserPicture
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
@@ -86,113 +89,30 @@ fun observeUserNickname(
|
||||
return flow.collectAsStateWithLifecycle(remember(user) { contactCards.cachedNickname(user) })
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeUserAboutMe(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<String> {
|
||||
// Subscribe in the relay for changes in the metadata of this user.
|
||||
UserFinderFilterAssemblerSubscription(user, accountViewModel)
|
||||
): State<String> = sharedObserveUserAboutMe(user, accountViewModel.dataSources().userFinder, accountViewModel.account)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(user) {
|
||||
user.metadata().flow.map {
|
||||
it?.info?.about ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(
|
||||
user
|
||||
.metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.about ?: "",
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeUserInfo(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<UserInfo?> {
|
||||
// Subscribe in the relay for changes in the metadata of this user.
|
||||
UserFinderFilterAssemblerSubscription(user, accountViewModel)
|
||||
): State<UserInfo?> = sharedObserveUserInfo(user, accountViewModel.dataSources().userFinder, accountViewModel.account)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
return user.metadata().flow.collectAsStateWithLifecycle()
|
||||
}
|
||||
|
||||
@SuppressLint("StateFlowValueCalledInComposition")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeUserBanner(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
): State<String?> {
|
||||
// Subscribe in the relay for changes in the metadata of this user.
|
||||
UserFinderFilterAssemblerSubscription(user, accountViewModel)
|
||||
): State<String?> = sharedObserveUserBanner(user, accountViewModel.dataSources().userFinder, accountViewModel.account)
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(user) {
|
||||
user
|
||||
.metadata()
|
||||
.flow
|
||||
.map {
|
||||
it?.info?.banner
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(
|
||||
user
|
||||
.metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.banner,
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("StateFlowValueCalledInComposition")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
fun observeUserPicture(
|
||||
user: User,
|
||||
accountViewModel: AccountViewModel,
|
||||
subscribe: Boolean = true,
|
||||
): State<String?> {
|
||||
// Subscribe in the relay for changes in the metadata of this user.
|
||||
// Callers that already hold a single shared subscription for the same user
|
||||
// (e.g. an author avatar that also observes the contact-card score) can pass
|
||||
// subscribe = false to avoid setting up a redundant relay subscription.
|
||||
if (subscribe) {
|
||||
UserFinderFilterAssemblerSubscription(user, accountViewModel)
|
||||
}
|
||||
|
||||
// Subscribe in the LocalCache for changes that arrive in the device
|
||||
val flow =
|
||||
remember(user) {
|
||||
user
|
||||
.metadata()
|
||||
.flow
|
||||
.map {
|
||||
it?.info?.picture
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
|
||||
return flow.collectAsStateWithLifecycle(
|
||||
user
|
||||
.metadataOrNull()
|
||||
?.flow
|
||||
?.value
|
||||
?.info
|
||||
?.picture,
|
||||
)
|
||||
}
|
||||
): State<String?> = sharedObserveUserPicture(user, accountViewModel.dataSources().userFinder, accountViewModel.account, subscribe)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
|
||||
@Composable
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.actions.ConcordActions
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||
import com.vitorpamplona.amethyst.debugState
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
|
||||
@@ -78,6 +78,7 @@ import com.vitorpamplona.amethyst.commons.resources.lightning_create_and_add_inv
|
||||
import com.vitorpamplona.amethyst.commons.resources.lightning_invoice
|
||||
import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.invoice.AddLnInvoiceButton
|
||||
@@ -98,7 +99,6 @@ import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery
|
||||
import com.vitorpamplona.amethyst.ui.components.BechLink
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadUrlPreview
|
||||
import com.vitorpamplona.amethyst.ui.components.OutlinedThinPaddingTextField
|
||||
import com.vitorpamplona.amethyst.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.invoice.InvoiceRequest
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.uploads.ImageVideoDescription
|
||||
|
||||
@@ -45,12 +45,12 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.copy_stack_to_clipboard
|
||||
import com.vitorpamplona.amethyst.commons.resources.error_dialog_button_ok
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
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.ui.components.toasts.ThrowableToastMsg
|
||||
import com.vitorpamplona.amethyst.ui.components.toasts.ThrowableToastMsg2
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
|
||||
@@ -73,6 +73,7 @@ import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_description
|
||||
import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_label
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.contentWarning.SettingSwitchItem
|
||||
@@ -82,7 +83,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery
|
||||
import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge
|
||||
import com.vitorpamplona.amethyst.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
+1
-1
@@ -75,12 +75,12 @@ import com.vitorpamplona.amethyst.commons.resources.twitter
|
||||
import com.vitorpamplona.amethyst.commons.resources.twitter_proof_url_template
|
||||
import com.vitorpamplona.amethyst.commons.resources.username
|
||||
import com.vitorpamplona.amethyst.commons.resources.website_url
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery
|
||||
import com.vitorpamplona.amethyst.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
+1
-1
@@ -63,6 +63,7 @@ import com.vitorpamplona.amethyst.commons.resources.bolt12_offers_section_explai
|
||||
import com.vitorpamplona.amethyst.commons.resources.delete_bolt12_offer
|
||||
import com.vitorpamplona.amethyst.commons.resources.invalid_bolt12_offer
|
||||
import com.vitorpamplona.amethyst.commons.resources.no_bolt12_offers_message
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
@@ -73,7 +74,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp
|
||||
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.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.abbreviateBolt12Offer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory
|
||||
|
||||
+1
-1
@@ -57,12 +57,12 @@ import com.vitorpamplona.amethyst.commons.resources.local_blossom_cache_profile_
|
||||
import com.vitorpamplona.amethyst.commons.resources.media_servers
|
||||
import com.vitorpamplona.amethyst.commons.resources.use_local_blossom_cache
|
||||
import com.vitorpamplona.amethyst.commons.resources.use_local_blossom_cache_caption
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.allGoodColor
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.grayText
|
||||
import com.vitorpamplona.amethyst.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
|
||||
+1
-1
@@ -128,6 +128,7 @@ import com.vitorpamplona.amethyst.commons.resources.manage_stored_files_empty
|
||||
import com.vitorpamplona.amethyst.commons.resources.my_blossom_data
|
||||
import com.vitorpamplona.amethyst.commons.resources.quick_action_share
|
||||
import com.vitorpamplona.amethyst.commons.resources.retry
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarExtensibleWithBackButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
|
||||
@@ -135,7 +136,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.allGoodColor
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.grayText
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.VideoViewInner
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportType
|
||||
|
||||
+1
-1
@@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.commons.resources.nests_servers_relay_label
|
||||
import com.vitorpamplona.amethyst.commons.resources.nests_servers_remove
|
||||
import com.vitorpamplona.amethyst.commons.resources.nests_servers_title
|
||||
import com.vitorpamplona.amethyst.commons.resources.nests_servers_use_defaults
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
@@ -82,7 +83,6 @@ 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.ui.actions.mediaServers.AllMediaServersScreen
|
||||
import com.vitorpamplona.amethyst.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategoryWithButton
|
||||
|
||||
+1
-1
@@ -61,6 +61,7 @@ import com.vitorpamplona.amethyst.commons.resources.payment_target_type
|
||||
import com.vitorpamplona.amethyst.commons.resources.payment_targets
|
||||
import com.vitorpamplona.amethyst.commons.resources.payment_targets_explainer
|
||||
import com.vitorpamplona.amethyst.commons.resources.payment_targets_section_explainer
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
@@ -71,7 +72,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp
|
||||
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.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory
|
||||
import com.vitorpamplona.quartz.experimental.nipA3.PaymentTarget
|
||||
|
||||
@@ -57,6 +57,7 @@ import com.vitorpamplona.amethyst.commons.resources.cashu_mint_label
|
||||
import com.vitorpamplona.amethyst.commons.resources.cashu_no_wallet_found
|
||||
import com.vitorpamplona.amethyst.commons.resources.cashu_redeem
|
||||
import com.vitorpamplona.amethyst.commons.resources.sats
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.OpenInNewIcon
|
||||
@@ -68,7 +69,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.service.cashu.CachedCashuParser
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip60Cashu.token.CashuToken
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -93,7 +93,7 @@ fun CashuPreview(
|
||||
}
|
||||
}
|
||||
|
||||
CrossfadeIfEnabled(targetState = cashuData, label = "CashuPreview", accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = cashuData, label = "CashuPreview") {
|
||||
when (it) {
|
||||
is GenericLoadable.Loaded<ImmutableList<CashuToken>> -> {
|
||||
CashuPreview(it.loaded, accountViewModel)
|
||||
|
||||
+1
-1
@@ -30,8 +30,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import com.vitorpamplona.quartz.buzz.invite.BuzzInviteLink
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
+1
-1
@@ -31,8 +31,8 @@ import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.vitorpamplona.amethyst.commons.actions.ConcordActions
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -30,8 +30,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import com.vitorpamplona.quartz.nip29RelayGroups.GroupInviteLink
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
+1
-1
@@ -30,8 +30,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -20,12 +20,6 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.InlineTextContent
|
||||
import androidx.compose.foundation.text.appendInlineContent
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -34,34 +28,29 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.text.LinkAnnotation
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
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.text.withLink
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.concord_invite_naddr_label
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.ClickableInLineIconRenderer
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CreateClickableTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CustomEmojiChecker
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.util.njumpLink
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||
@@ -84,9 +73,6 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NSec
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNIP19
|
||||
import com.vitorpamplona.quartz.nip29RelayGroups.GroupNAddrInvite
|
||||
import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
|
||||
@Composable
|
||||
fun ClickableRoute(
|
||||
@@ -406,208 +392,6 @@ fun CreateClickableText(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CustomEmojiChecker(
|
||||
text: String,
|
||||
tags: ImmutableListOfLists<String>?,
|
||||
onRegularText: @Composable (String) -> Unit,
|
||||
onEmojiText: @Composable (ImmutableList<CustomEmoji.Renderable>) -> Unit,
|
||||
) {
|
||||
val mayContainEmoji by remember(text, tags) {
|
||||
mutableStateOf(CustomEmoji.fastMightContainEmoji(text, tags?.lists))
|
||||
}
|
||||
|
||||
if (mayContainEmoji) {
|
||||
var emojiList by
|
||||
remember(text, tags) {
|
||||
mutableStateOf<ImmutableList<CustomEmoji.Renderable>?>(null)
|
||||
}
|
||||
|
||||
LaunchedEffect(text, tags) {
|
||||
val newEmojiList = CustomEmoji.assembleAnnotatedList(text, tags?.lists)
|
||||
if (newEmojiList != null) {
|
||||
emojiList = newEmojiList
|
||||
}
|
||||
}
|
||||
|
||||
emojiList?.let {
|
||||
onEmojiText(it)
|
||||
} ?: run {
|
||||
onRegularText(text)
|
||||
}
|
||||
} else {
|
||||
onRegularText(text)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CustomEmojiChecker(
|
||||
text: String,
|
||||
emojis: ImmutableMap<String, String>,
|
||||
onRegularText: @Composable (String) -> Unit,
|
||||
onEmojiText: @Composable (ImmutableList<CustomEmoji.Renderable>) -> Unit,
|
||||
) {
|
||||
val mayContainEmoji by remember(text, emojis) {
|
||||
mutableStateOf(CustomEmoji.fastMightContainEmoji(text, emojis))
|
||||
}
|
||||
|
||||
if (mayContainEmoji) {
|
||||
var emojiList by
|
||||
remember(text, emojis) {
|
||||
mutableStateOf<ImmutableList<CustomEmoji.Renderable>?>(null)
|
||||
}
|
||||
|
||||
LaunchedEffect(text, emojis) {
|
||||
val newEmojiList = CustomEmoji.assembleAnnotatedList(text, emojis)
|
||||
if (newEmojiList != null) {
|
||||
emojiList = newEmojiList
|
||||
}
|
||||
}
|
||||
|
||||
emojiList?.let {
|
||||
onEmojiText(it)
|
||||
} ?: run {
|
||||
onRegularText(text)
|
||||
}
|
||||
} else {
|
||||
onRegularText(text)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CreateTextWithEmoji(
|
||||
text: String,
|
||||
tags: ImmutableListOfLists<String>?,
|
||||
color: Color = Color.Unspecified,
|
||||
textAlign: TextAlign? = null,
|
||||
fontWeight: FontWeight? = null,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
CustomEmojiChecker(
|
||||
text,
|
||||
tags,
|
||||
onEmojiText = {
|
||||
val textColor =
|
||||
color.takeOrElse { LocalTextStyle.current.color.takeOrElse { LocalContentColor.current } }
|
||||
val style =
|
||||
LocalTextStyle.current
|
||||
.merge(
|
||||
TextStyle(
|
||||
color = textColor,
|
||||
textAlign = TextAlign.Unspecified,
|
||||
fontWeight = fontWeight,
|
||||
fontSize = fontSize,
|
||||
),
|
||||
).toSpanStyle()
|
||||
|
||||
InLineIconRenderer(it, style, fontSize, maxLines, overflow, modifier)
|
||||
},
|
||||
onRegularText = {
|
||||
val textColor =
|
||||
color.takeOrElse { LocalTextStyle.current.color.takeOrElse { LocalContentColor.current } }
|
||||
Text(
|
||||
text = it,
|
||||
color = textColor,
|
||||
textAlign = textAlign,
|
||||
fontWeight = fontWeight,
|
||||
fontSize = fontSize,
|
||||
maxLines = maxLines,
|
||||
overflow = overflow,
|
||||
modifier = modifier,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CreateTextWithEmoji(
|
||||
text: String,
|
||||
emojis: ImmutableMap<String, String>,
|
||||
color: Color = Color.Unspecified,
|
||||
textAlign: TextAlign? = null,
|
||||
fontWeight: FontWeight? = null,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val textColor =
|
||||
color.takeOrElse { LocalTextStyle.current.color.takeOrElse { LocalContentColor.current } }
|
||||
|
||||
CustomEmojiChecker(
|
||||
text,
|
||||
emojis,
|
||||
onEmojiText = {
|
||||
val currentStyle = LocalTextStyle.current
|
||||
val style =
|
||||
remember(currentStyle) {
|
||||
currentStyle
|
||||
.merge(
|
||||
TextStyle(
|
||||
color = textColor,
|
||||
textAlign = TextAlign.Unspecified,
|
||||
fontWeight = fontWeight,
|
||||
fontSize = fontSize,
|
||||
),
|
||||
).toSpanStyle()
|
||||
}
|
||||
|
||||
InLineIconRenderer(it, style, fontSize, maxLines, overflow, modifier)
|
||||
},
|
||||
onRegularText = {
|
||||
Text(
|
||||
text = it,
|
||||
color = textColor,
|
||||
textAlign = textAlign,
|
||||
fontWeight = fontWeight,
|
||||
fontSize = fontSize,
|
||||
maxLines = maxLines,
|
||||
overflow = overflow,
|
||||
modifier = modifier,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CreateClickableTextWithEmoji(
|
||||
clickablePart: String,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
tags: ImmutableListOfLists<String>?,
|
||||
style: TextStyle,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
CustomEmojiChecker(
|
||||
text = clickablePart,
|
||||
tags = tags,
|
||||
onRegularText = {
|
||||
Text(
|
||||
text =
|
||||
buildAnnotatedString {
|
||||
withLink(
|
||||
LinkAnnotation.Clickable(
|
||||
"me",
|
||||
TextLinkStyles(style = style.toSpanStyle()),
|
||||
) {
|
||||
onClick()
|
||||
},
|
||||
) {
|
||||
append(clickablePart)
|
||||
}
|
||||
},
|
||||
style = style,
|
||||
maxLines = maxLines,
|
||||
)
|
||||
},
|
||||
onEmojiText = {
|
||||
ClickableInLineIconRenderer(it, maxLines, style.toSpanStyle(), onClick = onClick)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CreateClickableTextWithEmoji(
|
||||
clickablePart: String,
|
||||
@@ -653,144 +437,3 @@ fun CreateClickableTextWithEmoji(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ClickableInLineIconRenderer(
|
||||
wordsInOrder: ImmutableList<CustomEmoji.Renderable>,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
style: SpanStyle,
|
||||
suffix: String? = null,
|
||||
nonClickableStype: SpanStyle? = null,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
val placeholderSize =
|
||||
remember(style) {
|
||||
if (style.fontSize == TextUnit.Unspecified) 22.sp else style.fontSize.times(1.1f)
|
||||
}
|
||||
|
||||
val inlineContent =
|
||||
wordsInOrder
|
||||
.mapIndexedNotNull { idx, value ->
|
||||
if (value is CustomEmoji.ImageUrlType) {
|
||||
Pair(
|
||||
"inlineContent$idx",
|
||||
InlineTextContent(
|
||||
Placeholder(
|
||||
width = placeholderSize,
|
||||
height = placeholderSize,
|
||||
placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
|
||||
),
|
||||
) {
|
||||
AsyncImage(
|
||||
model = value.url,
|
||||
contentDescription = null,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(1.dp),
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.associate { it.first to it.second }
|
||||
|
||||
val annotatedText =
|
||||
buildAnnotatedString {
|
||||
wordsInOrder.forEachIndexed { idx, value ->
|
||||
withLink(
|
||||
LinkAnnotation.Clickable("link", TextLinkStyles(style)) {
|
||||
onClick()
|
||||
},
|
||||
) {
|
||||
if (value is CustomEmoji.TextType) {
|
||||
append(value.text)
|
||||
} else if (value is CustomEmoji.ImageUrlType) {
|
||||
appendInlineContent("inlineContent$idx", "[icon]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (suffix != null && nonClickableStype != null) {
|
||||
withStyle(nonClickableStype) {
|
||||
append(suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = annotatedText,
|
||||
inlineContent = inlineContent,
|
||||
maxLines = maxLines,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InLineIconRenderer(
|
||||
wordsInOrder: ImmutableList<CustomEmoji.Renderable>,
|
||||
style: SpanStyle,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val placeholderSize =
|
||||
remember(fontSize) {
|
||||
if (fontSize == TextUnit.Unspecified) 22.sp else fontSize.times(1.1f)
|
||||
}
|
||||
|
||||
val inlineContent =
|
||||
wordsInOrder
|
||||
.mapIndexedNotNull { idx, value ->
|
||||
if (value is CustomEmoji.ImageUrlType) {
|
||||
Pair(
|
||||
"inlineContent$idx",
|
||||
InlineTextContent(
|
||||
Placeholder(
|
||||
width = placeholderSize,
|
||||
height = placeholderSize,
|
||||
placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
|
||||
),
|
||||
) {
|
||||
AsyncImage(
|
||||
model = value.url,
|
||||
contentDescription = null,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 0.dp),
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.associate { it.first to it.second }
|
||||
|
||||
val annotatedText =
|
||||
remember {
|
||||
buildAnnotatedString {
|
||||
wordsInOrder.forEachIndexed { idx, value ->
|
||||
withStyle(
|
||||
style,
|
||||
) {
|
||||
if (value is CustomEmoji.TextType) {
|
||||
append(value.text)
|
||||
} else if (value is CustomEmoji.ImageUrlType) {
|
||||
appendInlineContent("inlineContent$idx", "[icon]")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = annotatedText,
|
||||
inlineContent = inlineContent,
|
||||
fontSize = fontSize,
|
||||
maxLines = maxLines,
|
||||
overflow = overflow,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@ import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.error_dialog_pay_withdraw_error
|
||||
import com.vitorpamplona.amethyst.commons.resources.no_wallet_found
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.ClickableTextPrimary
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog
|
||||
import com.vitorpamplona.amethyst.ui.note.payViaIntent
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@@ -54,7 +54,7 @@ fun MayBeWithdrawal(
|
||||
launch(Dispatchers.IO) { lnWithdrawal = LnWithdrawalUtil.findWithdrawal(lnurlWord) }
|
||||
}
|
||||
|
||||
CrossfadeIfEnabled(targetState = lnWithdrawal, accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = lnWithdrawal) {
|
||||
if (it != null) {
|
||||
ClickableWithdrawal(withdrawalString = it)
|
||||
} else {
|
||||
|
||||
@@ -49,6 +49,7 @@ import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.concord_home_title
|
||||
import com.vitorpamplona.amethyst.commons.resources.concord_invite_card_join
|
||||
import com.vitorpamplona.amethyst.commons.resources.concord_invite_card_subtitle
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@@ -29,11 +29,11 @@ import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
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.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.UrlPreviewState
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.HalfVertPadding
|
||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
@@ -82,7 +82,6 @@ fun LoadUrlPreviewDirect(
|
||||
CrossfadeIfEnabled(
|
||||
targetState = urlPreviewState,
|
||||
label = "UrlPreview",
|
||||
accountViewModel = accountViewModel,
|
||||
) { state ->
|
||||
when (state) {
|
||||
is UrlPreviewState.Loaded -> {
|
||||
|
||||
@@ -37,13 +37,14 @@ import androidx.compose.ui.layout.ContentScale
|
||||
import coil3.compose.AsyncImagePainter
|
||||
import coil3.compose.SubcomposeAsyncImage
|
||||
import coil3.compose.SubcomposeAsyncImageContent
|
||||
import com.vitorpamplona.amethyst.commons.richtext.isAnimatedMediaUrl
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.DownloadForOfflineIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size40dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size6dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size75dp
|
||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
@@ -73,7 +74,7 @@ fun MyAsyncImage(
|
||||
val ratio = MediaAspectRatioCache.get(imageUrl)
|
||||
val showImage = remember { mutableStateOf(accountViewModel.settings.showImages()) }
|
||||
|
||||
CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center, accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center) {
|
||||
if (it) {
|
||||
SubcomposeAsyncImage(
|
||||
model = imageUrl,
|
||||
@@ -154,11 +155,3 @@ fun MyAsyncImage(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isAnimatedMediaUrl(url: String): Boolean =
|
||||
url.endsWith(".gif", ignoreCase = true) ||
|
||||
url.contains(".gif?", ignoreCase = true) ||
|
||||
url.contains(".gif#", ignoreCase = true) ||
|
||||
url.endsWith(".avif", ignoreCase = true) ||
|
||||
url.contains(".avif?", ignoreCase = true) ||
|
||||
url.contains(".avif#", ignoreCase = true)
|
||||
|
||||
@@ -50,10 +50,10 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.copied_to_clipboard
|
||||
import com.vitorpamplona.amethyst.commons.resources.copy_to_clipboard
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size18Modifier
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
|
||||
@@ -53,6 +53,7 @@ import com.vitorpamplona.amethyst.commons.resources.relay_group_badge_invite_onl
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_group_badge_private
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_group_member_count
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_group_open
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
|
||||
+1
-3
@@ -56,6 +56,7 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_
|
||||
import com.vitorpamplona.amethyst.commons.resources.no_wallet_found
|
||||
import com.vitorpamplona.amethyst.commons.resources.no_zap_amount_setup_long_press_to_change
|
||||
import com.vitorpamplona.amethyst.commons.resources.thank_you
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ZapIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ZappedIcon
|
||||
@@ -66,7 +67,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp
|
||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage
|
||||
import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup
|
||||
@@ -254,7 +254,6 @@ fun ReusableZapButton(
|
||||
CrossfadeIfEnabled(
|
||||
targetState = wasZappedByLoggedInUser.value,
|
||||
label = "ZapIcon",
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
if (it) {
|
||||
ZappedIcon(config.iconSizeModifier)
|
||||
@@ -286,7 +285,6 @@ fun ReusableZapButton(
|
||||
CrossfadeIfEnabled(
|
||||
targetState = wasZappedByLoggedInUser.value,
|
||||
label = "ZapIcon",
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
if (it) {
|
||||
ZappedIcon(config.iconSizeModifier)
|
||||
|
||||
@@ -70,6 +70,7 @@ import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.richtext.Base64Segment
|
||||
import com.vitorpamplona.amethyst.commons.richtext.BechSegment
|
||||
import com.vitorpamplona.amethyst.commons.richtext.BlossomUriSegment
|
||||
@@ -102,9 +103,12 @@ import com.vitorpamplona.amethyst.commons.richtext.VideoSegment
|
||||
import com.vitorpamplona.amethyst.commons.richtext.WithdrawSegment
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.AnimatedBorderTextCornerRadius
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.ClickableTextPrimary
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.NowhereLinkCard
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CreateClickableTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.HashtagIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.checkForHashtagWithIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.state.produceCachedState
|
||||
@@ -116,7 +120,6 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinder
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserNickname
|
||||
import com.vitorpamplona.amethyst.service.uploads.blossom.bud10.openBlossomUriAsIntent
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.invoice.ClinkOfferPreview
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.invoice.MayBeInvoicePreview
|
||||
@@ -977,7 +980,7 @@ private fun DisplayUserFromTag(
|
||||
val nickname by observeUserNickname(baseUser, accountViewModel)
|
||||
val petName = nickname?.petName
|
||||
|
||||
CrossfadeIfEnabled(targetState = meta, label = "DisplayUserFromTag", accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = meta, label = "DisplayUserFromTag") {
|
||||
Row {
|
||||
CreateClickableTextWithEmoji(
|
||||
clickablePart = remember(meta, petName) { petName ?: it?.info?.bestName() ?: baseUser.pubkeyDisplayHex() },
|
||||
|
||||
+3
-3
@@ -67,12 +67,12 @@ import com.vitorpamplona.amethyst.commons.resources.content_warning_explanation
|
||||
import com.vitorpamplona.amethyst.commons.resources.content_warning_with_reason
|
||||
import com.vitorpamplona.amethyst.commons.resources.show_anyway
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ButtonPadding
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.PaddingHorizontal12Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip36SensitiveContent.ContentWarningTag
|
||||
@@ -154,7 +154,7 @@ fun ContentWarningGate(
|
||||
}
|
||||
}
|
||||
|
||||
CrossfadeIfEnabled(targetState = showContentWarningNote, accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = showContentWarningNote) {
|
||||
if (it) {
|
||||
if (backdrop != null) {
|
||||
Box(modifier = modifier.clipToBounds()) {
|
||||
@@ -180,7 +180,7 @@ fun ObserveSensitivityWarning(
|
||||
|
||||
var showContentWarningNote by remember(accountState) { mutableStateOf(accountState.value != true) }
|
||||
|
||||
CrossfadeIfEnabled(targetState = showContentWarningNote, accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = showContentWarningNote) {
|
||||
if (it) {
|
||||
ContentWarningNote(reason) { showContentWarningNote = false }
|
||||
} else {
|
||||
|
||||
+1
@@ -29,6 +29,7 @@ import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.window.DialogWindowProvider
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.getActivityWindow
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.isLight
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -51,13 +51,13 @@ import com.vitorpamplona.amethyst.commons.resources.url_preview_open_in_browser
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionDialog
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionRow
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionSection
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.MaxWidthWithHorzPadding
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size14Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.innerPostModifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.previewCardImageModifier
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
||||
@@ -20,31 +20,13 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.view.Window
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.window.DialogWindowProvider
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
|
||||
// Window utils
|
||||
@Composable
|
||||
fun getDialogWindow(): Window? = (LocalView.current.parent as? DialogWindowProvider)?.window
|
||||
|
||||
@Composable
|
||||
fun getActivityWindow(): Window? = LocalView.current.context.getActivityWindow()
|
||||
|
||||
private tailrec fun Context.getActivityWindow(): Window? =
|
||||
when (this) {
|
||||
is Activity -> window
|
||||
is ContextWrapper -> baseContext.getActivityWindow()
|
||||
else -> null
|
||||
}
|
||||
|
||||
tailrec fun Context.getActivity(): ComponentActivity =
|
||||
when (this) {
|
||||
is ComponentActivity -> this
|
||||
|
||||
+2
@@ -80,6 +80,8 @@ 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.ui.components.getActivityWindow
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.getDialogWindow
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier
|
||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||
|
||||
+6
-10
@@ -104,11 +104,14 @@ 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.RichTextParser
|
||||
import com.vitorpamplona.amethyst.commons.richtext.isAnimatedMediaUrl
|
||||
import com.vitorpamplona.amethyst.commons.service.image.placeholderModel
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionDialog
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionRow
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionSection
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.DownloadForOfflineIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
@@ -124,11 +127,9 @@ 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.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.actions.InformationDialog
|
||||
import com.vitorpamplona.amethyst.ui.components.pdf.PdfPreviewCard
|
||||
import com.vitorpamplona.amethyst.ui.components.pdf.PdfViewerDialog
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.ui.note.BlankNote
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
@@ -413,7 +414,7 @@ fun LocalImageView(
|
||||
} else {
|
||||
content.localFile
|
||||
}
|
||||
CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center, accountViewModel = accountViewModel) { imageVisible ->
|
||||
CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center) { imageVisible ->
|
||||
if (imageVisible) {
|
||||
SubcomposeAsyncImage(
|
||||
model = imageModel,
|
||||
@@ -546,7 +547,7 @@ fun UrlImageView(
|
||||
content.url
|
||||
}
|
||||
|
||||
CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center, accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = showImage.value, contentAlignment = Alignment.Center) {
|
||||
if (it) {
|
||||
SubcomposeAsyncImage(
|
||||
model = imageModel,
|
||||
@@ -749,12 +750,7 @@ fun BaseMediaContent.isAnimatedMedia(): Boolean =
|
||||
if (this is MediaUrlContent) {
|
||||
mimeType == "image/gif" ||
|
||||
mimeType == "image/avif" ||
|
||||
url.endsWith(".gif", ignoreCase = true) ||
|
||||
url.contains(".gif?", ignoreCase = true) ||
|
||||
url.contains(".gif#", ignoreCase = true) ||
|
||||
url.endsWith(".avif", ignoreCase = true) ||
|
||||
url.contains(".avif?", ignoreCase = true) ||
|
||||
url.contains(".avif#", ignoreCase = true)
|
||||
isAnimatedMediaUrl(url)
|
||||
} else if (this is MediaPreloadedContent) {
|
||||
mimeType == "image/gif" ||
|
||||
mimeType == "image/avif" ||
|
||||
|
||||
+1
-1
@@ -62,6 +62,7 @@ import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.core.graphics.createBitmap
|
||||
import coil3.disk.DiskCache
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlPdf
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.getDialogWindow
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.ui.components.ImmersiveSystemBarsEffect
|
||||
@@ -69,7 +70,6 @@ import com.vitorpamplona.amethyst.ui.components.ViewerBackButton
|
||||
import com.vitorpamplona.amethyst.ui.components.ViewerControlsRow
|
||||
import com.vitorpamplona.amethyst.ui.components.ViewerSaveToGalleryButton
|
||||
import com.vitorpamplona.amethyst.ui.components.ViewerShareButton
|
||||
import com.vitorpamplona.amethyst.ui.components.getDialogWindow
|
||||
import com.vitorpamplona.amethyst.ui.components.rememberViewerControlsVisibility
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
|
||||
+1
@@ -36,6 +36,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.copied_to_clipboard
|
||||
import com.vitorpamplona.amethyst.commons.resources.copy_to_clipboard
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -30,6 +30,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.feeds.FeedContentState
|
||||
import com.vitorpamplona.amethyst.commons.feeds.FeedState
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedEmpty
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedError
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.LoadingFeed
|
||||
@@ -37,7 +38,6 @@ import com.vitorpamplona.amethyst.commons.ui.feeds.RefresheableBox
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.rememberForeverLazyGridState
|
||||
import com.vitorpamplona.amethyst.commons.ui.feeds.rememberForeverLazyListState
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
@@ -111,7 +111,6 @@ fun RenderFeedContentState(
|
||||
CrossfadeIfEnabled(
|
||||
targetState = feedState,
|
||||
animationSpec = tween(durationMillis = 100),
|
||||
accountViewModel = accountViewModel,
|
||||
) { state ->
|
||||
when (state) {
|
||||
is FeedState.Empty -> onEmpty()
|
||||
|
||||
+17
-337
@@ -20,57 +20,17 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.layouts
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.WindowInsetsSides
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.only
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingBarNestedScroll
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingBarState
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.LocalDisappearingBarState
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.LocalDisappearingScaffoldPadding
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.rememberDisappearingBarState
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.components.getActivityWindow
|
||||
import com.vitorpamplona.amethyst.ui.insets.SafeImeInsets
|
||||
import com.vitorpamplona.amethyst.ui.insets.rememberSafeImeInsets
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.LocalScreenLayout
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private enum class DisappearingSlot { Top, Bottom, Content, Fab }
|
||||
|
||||
private val FabEdgePadding = 16.dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold as SharedDisappearingScaffold
|
||||
|
||||
/**
|
||||
* The app's [SharedDisappearingScaffold]: bars hide on scroll only when the user's immersive
|
||||
* scrolling setting is on, and never on large screens (rail / permanent drawer), which pin the
|
||||
* chrome.
|
||||
*/
|
||||
@Composable
|
||||
fun DisappearingScaffold(
|
||||
isInvertedLayout: Boolean,
|
||||
@@ -81,293 +41,13 @@ fun DisappearingScaffold(
|
||||
isActive: () -> Boolean = { true },
|
||||
allowBarHide: Boolean = true,
|
||||
mainContent: @Composable (padding: PaddingValues) -> Unit,
|
||||
) {
|
||||
val state = rememberDisappearingBarState()
|
||||
|
||||
// Large screens (rail / permanent drawer) pin the chrome: bars never slide away on
|
||||
// scroll, and the immersive status-bar hiding stays off.
|
||||
val canHideBars = allowBarHide && !LocalScreenLayout.current.isLargeScreen
|
||||
|
||||
// Hold the latest values in state so the NSC's captured lambda stays fresh across
|
||||
// recompositions without rebuilding the NSC itself.
|
||||
val latestIsActive by rememberUpdatedState(isActive)
|
||||
val latestAllowBarHide by rememberUpdatedState(canHideBars)
|
||||
val latestAccountViewModel by rememberUpdatedState(accountViewModel)
|
||||
|
||||
val connection =
|
||||
remember(state, isInvertedLayout) {
|
||||
DisappearingBarNestedScroll(
|
||||
state = state,
|
||||
canScroll = {
|
||||
latestAllowBarHide &&
|
||||
latestIsActive() &&
|
||||
latestAccountViewModel.settings.isImmersiveScrollingActive()
|
||||
},
|
||||
reverseLayout = isInvertedLayout,
|
||||
)
|
||||
}
|
||||
|
||||
// Only wire the lifecycle observer + system-bar control when the scaffold actually moves its bars.
|
||||
if (canHideBars) {
|
||||
ResetBarsOnResume(state)
|
||||
ImmersiveStatusBarEffect(state)
|
||||
}
|
||||
|
||||
// If the bars were scrolled away when hiding got disabled (e.g. the window grew to a
|
||||
// large tier mid-scroll), nothing above can bring them back — the nested-scroll
|
||||
// connection and the resume reset are gone. Snap them visible here instead of
|
||||
// leaving the chrome stranded off-screen.
|
||||
LaunchedEffect(canHideBars, state) {
|
||||
if (!canHideBars) state.resetToVisible()
|
||||
}
|
||||
|
||||
// When bars are pinned, skip attaching the nested-scroll connection entirely.
|
||||
// The outer Surface provides the Material container color + onBackground as
|
||||
// LocalContentColor, matching M3 Scaffold's behaviour (without it, default text
|
||||
// color falls back to Color.Black and is invisible on the dark theme).
|
||||
// Hoisted above the branch: imePaddingSafe() inside both arms would put the call in two
|
||||
// different composition groups, so toggling canHideBars (a window-size-class change) would
|
||||
// dispose and rebuild it. Resolved once here, and handed to ScaffoldLayout below so the value
|
||||
// this pads with is the same object the nav-bar subtraction reads.
|
||||
val imeInsets = rememberSafeImeInsets()
|
||||
val baseModifier =
|
||||
if (canHideBars) {
|
||||
Modifier.windowInsetsPadding(imeInsets).nestedScroll(connection)
|
||||
} else {
|
||||
Modifier.windowInsetsPadding(imeInsets)
|
||||
}
|
||||
val rootModifier =
|
||||
baseModifier
|
||||
// systemBars (not just statusBars) so a desktop window's caption bar is respected too.
|
||||
.let { if (topBar == null) it.windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Top)) else it }
|
||||
.let { if (bottomBar == null) it.navigationBarsPadding() else it }
|
||||
|
||||
Surface(
|
||||
modifier = rootModifier,
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
contentColor = MaterialTheme.colorScheme.onBackground,
|
||||
) {
|
||||
ScaffoldLayout(
|
||||
state = state,
|
||||
topBar = topBar,
|
||||
bottomBar = bottomBar,
|
||||
floatingButton = floatingButton,
|
||||
imeInsets = imeInsets,
|
||||
mainContent = mainContent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ScaffoldLayout(
|
||||
state: DisappearingBarState,
|
||||
topBar: (@Composable () -> Unit)?,
|
||||
bottomBar: (@Composable () -> Unit)?,
|
||||
floatingButton: (@Composable () -> Unit)?,
|
||||
imeInsets: SafeImeInsets,
|
||||
mainContent: @Composable (padding: PaddingValues) -> Unit,
|
||||
) {
|
||||
val navBarInsets = WindowInsets.navigationBars
|
||||
SubcomposeLayout { constraints ->
|
||||
val layoutWidth = constraints.maxWidth
|
||||
val layoutHeight = constraints.maxHeight
|
||||
val looseConstraints = constraints.copy(minWidth = 0, minHeight = 0)
|
||||
|
||||
val topPlaceable =
|
||||
topBar?.let { bar ->
|
||||
subcompose(DisappearingSlot.Top) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.graphicsLayer {
|
||||
translationY = state.topHeightOffset
|
||||
},
|
||||
) {
|
||||
bar()
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
}
|
||||
}.firstOrNull()?.measure(looseConstraints)
|
||||
}
|
||||
val topHeight = topPlaceable?.height ?: 0
|
||||
|
||||
val bottomPlaceable =
|
||||
bottomBar?.let { bar ->
|
||||
subcompose(DisappearingSlot.Bottom) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier.graphicsLayer {
|
||||
translationY = -state.bottomHeightOffset
|
||||
},
|
||||
) {
|
||||
bar()
|
||||
}
|
||||
}.firstOrNull()?.measure(looseConstraints)
|
||||
}
|
||||
// When the bar lambda is provided but its content emits nothing (e.g. AppBottomBar
|
||||
// hides itself on canPop entries, or while the keyboard is up), reserve the
|
||||
// system-nav-bar inset so the FAB and content stay clear of the navigation bar instead
|
||||
// of sliding under it. Subtract the IME inset: the root imePadding has already lifted the
|
||||
// whole scaffold above the keyboard, and the IME inset spans the nav-bar band, so
|
||||
// reserving the full nav bar on top of that would double-count and strand a gap above the
|
||||
// keyboard (the `bottomBar == null` branch gets this for free via navigationBarsPadding,
|
||||
// which excludes the consumed IME inset). The `bottomBar == null` case is on rootModifier.
|
||||
val bottomHeight =
|
||||
(bottomPlaceable?.height ?: 0).let { measured ->
|
||||
if (bottomBar != null && measured == 0) {
|
||||
(navBarInsets.getBottom(this) - imeInsets.getBottom(this)).coerceAtLeast(0)
|
||||
} else {
|
||||
measured
|
||||
}
|
||||
}
|
||||
|
||||
// Publish the measured limits so the nested-scroll connection can clamp correctly.
|
||||
state.topHeightLimit = topHeight.toFloat()
|
||||
state.bottomHeightLimit = bottomHeight.toFloat()
|
||||
|
||||
val contentPadding =
|
||||
PaddingValues(
|
||||
top = topHeight.toDp(),
|
||||
bottom = bottomHeight.toDp(),
|
||||
)
|
||||
|
||||
val contentPlaceable =
|
||||
subcompose(DisappearingSlot.Content) {
|
||||
CompositionLocalProvider(
|
||||
LocalDisappearingScaffoldPadding provides contentPadding,
|
||||
LocalDisappearingBarState provides state,
|
||||
) {
|
||||
mainContent(contentPadding)
|
||||
}
|
||||
}.firstOrNull()?.measure(
|
||||
Constraints.fixed(layoutWidth, layoutHeight),
|
||||
)
|
||||
|
||||
val fabPlaceable =
|
||||
floatingButton?.let { fab ->
|
||||
subcompose(DisappearingSlot.Fab) {
|
||||
FloatingButtonHolder(state = state) { fab() }
|
||||
}.firstOrNull()?.measure(looseConstraints)
|
||||
}
|
||||
|
||||
val fabEdgePx = FabEdgePadding.roundToPx()
|
||||
|
||||
layout(layoutWidth, layoutHeight) {
|
||||
contentPlaceable?.place(0, 0)
|
||||
topPlaceable?.place(0, 0)
|
||||
bottomPlaceable?.place(0, layoutHeight - bottomHeight)
|
||||
if (fabPlaceable != null) {
|
||||
val x = layoutWidth - fabPlaceable.width - fabEdgePx
|
||||
val yBase = layoutHeight - bottomHeight - fabPlaceable.height - fabEdgePx
|
||||
fabPlaceable.place(x, yBase)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the floating button. Scales / fades it with the bottom-bar collapse fraction and
|
||||
* rides along with the bar via graphicsLayer, so the layout pass is untouched while
|
||||
* the bar animates.
|
||||
*/
|
||||
@Composable
|
||||
private fun FloatingButtonHolder(
|
||||
state: DisappearingBarState,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.graphicsLayer {
|
||||
val visible = (1f - state.bottomCollapsedFraction).coerceAtLeast(0.001f)
|
||||
scaleX = visible
|
||||
scaleY = visible
|
||||
alpha = visible
|
||||
translationY = -state.bottomHeightOffset
|
||||
},
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ResetBarsOnResume(state: DisappearingBarState) {
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val scope = rememberCoroutineScope()
|
||||
DisposableEffect(lifecycleOwner, state) {
|
||||
val observer =
|
||||
LifecycleEventObserver { _, event ->
|
||||
if (event == Lifecycle.Event.ON_RESUME &&
|
||||
(state.topHeightOffset != 0f || state.bottomHeightOffset != 0f)
|
||||
) {
|
||||
scope.launch { state.resetToVisible() }
|
||||
}
|
||||
}
|
||||
lifecycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
|
||||
}
|
||||
}
|
||||
|
||||
/** Fraction at which the tracked in-app bar is considered fully settled into the hidden edge. */
|
||||
private const val STATUS_BAR_HIDE_THRESHOLD = 0.999f
|
||||
|
||||
/**
|
||||
* Fraction the chrome must fall back below before the OS status bar reappears. Lower than
|
||||
* [STATUS_BAR_HIDE_THRESHOLD] on purpose: this hysteresis gap means the finger that stops a fast
|
||||
* scroll — which makes a tiny reverse reveal — does not immediately pop the status bar back. The
|
||||
* user has to deliberately scroll the chrome back by ~30% before the status bar returns.
|
||||
*/
|
||||
private const val STATUS_BAR_SHOW_THRESHOLD = 0.7f
|
||||
|
||||
/**
|
||||
* Hides the OS status bar once the tracked in-app bar has settled into the hidden edge, and shows it
|
||||
* the moment it starts coming back. The OS status bar is binary (cannot slide), so it is driven off
|
||||
* the collapse fraction with a near-1 threshold, which means it toggles on settle rather than
|
||||
* mid-drag.
|
||||
*
|
||||
* The status bar sits with the top in-app bar, so it tracks the top bar's collapse. The top and
|
||||
* bottom bars can collapse at different rates (each clamps to its own height), so tracking the top
|
||||
* fraction keeps the status bar in step with the chrome directly beneath it. On screens with no top
|
||||
* bar (topHeightLimit == 0) it falls back to the bottom bar, so the status bar still hides in sync
|
||||
* with the bottom navigation.
|
||||
*
|
||||
* Only the top status bar is touched; the bottom OS navigation bar is left alone.
|
||||
* BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE lets the user swipe to peek the status bar while immersed.
|
||||
* The status bar is always restored on dispose so leaving an immersive screen can never strand a
|
||||
* hidden bar.
|
||||
*/
|
||||
@Composable
|
||||
private fun ImmersiveStatusBarEffect(state: DisappearingBarState) {
|
||||
val view = LocalView.current
|
||||
if (view.isInEditMode) return
|
||||
val window = getActivityWindow() ?: return
|
||||
val controller = remember(window, view) { WindowInsetsControllerCompat(window, view) }
|
||||
|
||||
LaunchedEffect(controller, state) {
|
||||
controller.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
var statusBarHidden = false
|
||||
snapshotFlow {
|
||||
if (state.topHeightLimit > 0f) state.topCollapsedFraction else state.bottomCollapsedFraction
|
||||
}.distinctUntilChanged()
|
||||
.collect { fraction ->
|
||||
// Hysteresis: hide once fully settled, but only show again after the chrome has
|
||||
// been pulled back past STATUS_BAR_SHOW_THRESHOLD. Between the two thresholds the
|
||||
// status bar keeps its current state, so a stray reverse reveal can't flip it.
|
||||
val shouldHide =
|
||||
when {
|
||||
fraction >= STATUS_BAR_HIDE_THRESHOLD -> true
|
||||
fraction <= STATUS_BAR_SHOW_THRESHOLD -> false
|
||||
else -> statusBarHidden
|
||||
}
|
||||
if (shouldHide != statusBarHidden) {
|
||||
statusBarHidden = shouldHide
|
||||
if (shouldHide) {
|
||||
controller.hide(WindowInsetsCompat.Type.statusBars())
|
||||
} else {
|
||||
controller.show(WindowInsetsCompat.Type.statusBars())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(controller) {
|
||||
onDispose { controller.show(WindowInsetsCompat.Type.statusBars()) }
|
||||
}
|
||||
}
|
||||
) = SharedDisappearingScaffold(
|
||||
isInvertedLayout = isInvertedLayout,
|
||||
topBar = topBar,
|
||||
bottomBar = bottomBar,
|
||||
floatingButton = floatingButton,
|
||||
immersiveScrolling = { accountViewModel.settings.isImmersiveScrollingActive() },
|
||||
isActive = isActive,
|
||||
allowBarHide = allowBarHide && !LocalScreenLayout.current.isLargeScreen,
|
||||
mainContent = mainContent,
|
||||
)
|
||||
|
||||
@@ -29,52 +29,14 @@ import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSiz
|
||||
import androidx.compose.material3.windowsizeclass.WindowSizeClass
|
||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/** How the app shell presents its top-level navigation for the current window size. */
|
||||
enum class NavigationStyle {
|
||||
/** Compact windows (phones): bottom navigation bar + modal drawer. */
|
||||
BOTTOM_BAR,
|
||||
|
||||
/**
|
||||
* Every non-Compact window that does not dock — portrait tablets and unfolded foldables at
|
||||
* any width, plus short landscape windows: a left navigation rail replaces the bottom bar
|
||||
* and the drawer stays modal behind the rail's avatar button.
|
||||
*/
|
||||
NAV_RAIL,
|
||||
|
||||
/** Wide, landscape, tall windows (landscape tablets, desktop): the drawer docks permanently on the left. */
|
||||
PERMANENT_DRAWER,
|
||||
}
|
||||
|
||||
/**
|
||||
* The shell layout decisions for the current window, published once per window size change
|
||||
* through [LocalScreenLayout] so every screen, bar and panel agrees on the same tier.
|
||||
*/
|
||||
@Immutable
|
||||
data class ScreenLayoutSpec(
|
||||
val navigationStyle: NavigationStyle,
|
||||
val hasRoomForNotificationPanel: Boolean,
|
||||
) {
|
||||
/**
|
||||
* True on the rail and permanent-drawer tiers. Large screens hide the bottom bar and pin
|
||||
* the top/bottom chrome (no disappearing bars on scroll).
|
||||
*/
|
||||
val isLargeScreen: Boolean get() = navigationStyle != NavigationStyle.BOTTOM_BAR
|
||||
|
||||
companion object {
|
||||
val Phone = ScreenLayoutSpec(NavigationStyle.BOTTOM_BAR, hasRoomForNotificationPanel = false)
|
||||
}
|
||||
}
|
||||
|
||||
val LocalScreenLayout = compositionLocalOf { ScreenLayoutSpec.Phone }
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.NavigationStyle
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.ScreenLayoutSpec
|
||||
|
||||
/**
|
||||
* Minimum window width for the docked notification panel: a leading navigation pane, a
|
||||
|
||||
@@ -58,6 +58,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.user.LocalUserFinderAccoun
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.invalid_nip19_uri
|
||||
import com.vitorpamplona.amethyst.commons.resources.invalid_nip19_uri_description
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.LocalScreenLayout
|
||||
import com.vitorpamplona.amethyst.service.crashreports.DisplayCrashMessages
|
||||
import com.vitorpamplona.amethyst.service.relayClient.authCommand.compose.RelayAuthPromptHost
|
||||
import com.vitorpamplona.amethyst.service.relayClient.notifyCommand.compose.DisplayNotifyMessages
|
||||
@@ -74,7 +75,6 @@ import com.vitorpamplona.amethyst.ui.broadcast.DisplayBroadcastProgress
|
||||
import com.vitorpamplona.amethyst.ui.call.CallActivity
|
||||
import com.vitorpamplona.amethyst.ui.components.getActivity
|
||||
import com.vitorpamplona.amethyst.ui.components.toasts.DisplayErrorMessages
|
||||
import com.vitorpamplona.amethyst.ui.layouts.LocalScreenLayout
|
||||
import com.vitorpamplona.amethyst.ui.layouts.rememberScreenLayoutSpec
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.LocalTabReselectCoordinator
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.TabReselectCoordinator
|
||||
|
||||
+1
-1
@@ -49,6 +49,7 @@ import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppIcon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.BottomBarEntry
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.LocalScreenLayout
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness
|
||||
@@ -61,7 +62,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.onSurface65
|
||||
import com.vitorpamplona.amethyst.favorites.BrowserIconRegistry
|
||||
import com.vitorpamplona.amethyst.favorites.FavoriteAppsRegistry
|
||||
import com.vitorpamplona.amethyst.favorites.rememberNappletIconModel
|
||||
import com.vitorpamplona.amethyst.ui.layouts.LocalScreenLayout
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
/** Content height of the [AppBottomBar] (the 50.dp Column inside [RenderBottomMenu]),
|
||||
|
||||
+1
-1
@@ -41,10 +41,10 @@ import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChann
|
||||
import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.concord_home_title
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.rememberConcordImageModel
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||
|
||||
+3
-4
@@ -25,8 +25,7 @@ import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import com.vitorpamplona.amethyst.ui.insets.SafeImeInsets
|
||||
import com.vitorpamplona.amethyst.ui.insets.rememberSafeImeInsets
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.rememberSafeImeInsets
|
||||
|
||||
enum class KeyboardState {
|
||||
Opened,
|
||||
@@ -36,7 +35,7 @@ enum class KeyboardState {
|
||||
/**
|
||||
* Whether the soft keyboard is currently on screen, derived from the IME inset.
|
||||
*
|
||||
* This intentionally reads the same [SafeImeInsets] that drives `Modifier.imePaddingSafe()`
|
||||
* This intentionally reads the same [SafeImeInsets][com.vitorpamplona.amethyst.commons.ui.insets.SafeImeInsets] that drives `Modifier.imePaddingSafe()`
|
||||
* everywhere else in the app, so the two can never disagree about whether the keyboard is up.
|
||||
*
|
||||
* The original implementation measured `View.getWindowVisibleDisplayFrame` from a
|
||||
@@ -48,7 +47,7 @@ enum class KeyboardState {
|
||||
*
|
||||
* Reading `WindowInsets.ime` fixed that, but has a latch of its own: Compose stops updating the
|
||||
* inset entirely once its insets listener is left mid-animation, which pins this back at [Opened]
|
||||
* in exactly the same way. [SafeImeInsets] is what closes that last hole, which is why this reads
|
||||
* in exactly the same way. [SafeImeInsets][com.vitorpamplona.amethyst.commons.ui.insets.SafeImeInsets] is what closes that last hole, which is why this reads
|
||||
* the corrected inset rather than the raw one.
|
||||
*/
|
||||
@Composable
|
||||
|
||||
+2
-2
@@ -71,15 +71,15 @@ import com.vitorpamplona.amethyst.commons.resources.scheduled_posts_logout_toast
|
||||
import com.vitorpamplona.amethyst.commons.resources.scheduled_posts_logout_toast_zero
|
||||
import com.vitorpamplona.amethyst.commons.resources.scheduled_posts_logout_warning
|
||||
import com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPostStatus
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadPluralStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.AccountPictureModifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size55dp
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
+3
-2
@@ -97,6 +97,7 @@ import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.NavBarItem
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.bookmarks
|
||||
import com.vitorpamplona.amethyst.commons.resources.drafts
|
||||
@@ -116,8 +117,10 @@ import com.vitorpamplona.amethyst.commons.resources.share_hls_video
|
||||
import com.vitorpamplona.amethyst.commons.resources.show_npub_as_a_qr_code
|
||||
import com.vitorpamplona.amethyst.commons.resources.status_update
|
||||
import com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPostStatus
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.DoubleHorzSpacer
|
||||
@@ -145,8 +148,6 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNo
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserContactCardsFollowerCount
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserStatuses
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.layouts.PermanentDrawerWidth
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCatalog
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarItemDef
|
||||
|
||||
@@ -26,9 +26,8 @@ import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.rememberSafeImeInsets
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.ui.insets.SafeImeInsets
|
||||
import com.vitorpamplona.amethyst.ui.insets.rememberSafeImeInsets
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
|
||||
@@ -44,7 +43,7 @@ const val IME_SETTLE_TIMEOUT_MS = 700L
|
||||
* [WindowInsetsAnimationCompat][androidx.core.view.WindowInsetsAnimationCompat] is cancelled before
|
||||
* its terminal (zero) frame reaches Compose. `WindowInsets.ime` is a single app-wide holder, so it
|
||||
* stays "animating" and every IME padding in the app — not just the screen being left — freezes at
|
||||
* the keyboard height. [SafeImeInsets] is the backstop for when that freeze turns out to be
|
||||
* the keyboard height. [SafeImeInsets][com.vitorpamplona.amethyst.commons.ui.insets.SafeImeInsets] is the backstop for when that freeze turns out to be
|
||||
* permanent; this is the prevention, and it is the cheaper of the two because it keeps the padding
|
||||
* from ever being wrong.
|
||||
*
|
||||
@@ -64,7 +63,7 @@ fun interface ImeSettler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the same [SafeImeInsets] that drives `Modifier.imePaddingSafe()`, so the settler and the
|
||||
* Reads the same [SafeImeInsets][com.vitorpamplona.amethyst.commons.ui.insets.SafeImeInsets] that drives `Modifier.imePaddingSafe()`, so the settler and the
|
||||
* padding can never disagree about whether the keyboard is gone. Reading the raw `WindowInsets.ime`
|
||||
* here would make every navigation burn the full [IME_SETTLE_TIMEOUT_MS] once the inset is stranded,
|
||||
* waiting on a value that is never going to move again.
|
||||
|
||||
+2
-51
@@ -24,17 +24,16 @@ import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.concord.ConcordChannel
|
||||
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||
import com.vitorpamplona.amethyst.commons.model.marmotGroups.MarmotGroupChatroom
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.minichatRouteFor
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel
|
||||
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.buzz.notifications.MemberAddedNotificationEvent
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId
|
||||
import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.SoftwareApplicationEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
@@ -79,34 +78,6 @@ import com.vitorpamplona.quartz.nipA4PublicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.nipCCGeocaching.curation.GeocacheCurationListEvent
|
||||
import com.vitorpamplona.quartz.nipCCGeocaching.listing.GeocacheListingEvent
|
||||
|
||||
/**
|
||||
* A minichat reply — a kind-1111 [CommentEvent] posted into a chat message's thread — should open
|
||||
* that message's minichat ([Route.ChatMinichat]), not the whole channel it lives in. Regular chat
|
||||
* messages are kind 9 / 42, so a [CommentEvent] in a *chat context* is always a thread reply. We
|
||||
* gate on the chat context (the reply is attached to a Concord / relay-group / public-chat gatherer,
|
||||
* or its root message is) precisely so a generic NIP-22 comment on an article or note keeps its own
|
||||
* thread route and isn't mistaken for a minichat. Returns null when it isn't a chat-context comment.
|
||||
*/
|
||||
fun minichatRouteFor(note: Note): Route? {
|
||||
val comment = note.event as? CommentEvent ?: return null
|
||||
val rootId = comment.rootEventIds().firstOrNull() ?: return null
|
||||
|
||||
// Prefer the reply's own Concord channel (the reply arrived over that plane, so its gatherer
|
||||
// always carries the community/channel), else the root note's if it happens to be loaded. Passing
|
||||
// these lets the minichat screen resolve the plane + relays even when the parent isn't cached.
|
||||
val concord =
|
||||
note.inGatherers?.firstNotNullOfOrNull { it as? ConcordChannel }
|
||||
?: LocalCache.getNoteIfExists(rootId)?.inGatherers?.firstNotNullOfOrNull { it as? ConcordChannel }
|
||||
if (concord != null) {
|
||||
return Route.ChatMinichat(rootId, concord.channelId.communityId, concord.channelId.channelId)
|
||||
}
|
||||
|
||||
val inChatContext = note.isInChatGatherer() || LocalCache.getNoteIfExists(rootId)?.isInChatGatherer() == true
|
||||
return if (inChatContext) Route.ChatMinichat(rootId) else null
|
||||
}
|
||||
|
||||
private fun Note.isInChatGatherer(): Boolean = inGatherers?.any { it is ConcordChannel || it is RelayGroupChannel || it is PublicChatChannel } == true
|
||||
|
||||
/**
|
||||
* Kinds whose destination is `Route.Note(id)` — the generic thread view — no matter what the
|
||||
* event body turns out to say. Mirrors the `else ->` branch of [routeForInner]: every kind here
|
||||
@@ -419,26 +390,6 @@ fun routeToMessage(
|
||||
accountViewModel: AccountViewModel,
|
||||
): Route = routeToMessage(user.pubkeyHex, draftMessage, replyId, draftId, expiresDays, accountViewModel)
|
||||
|
||||
fun routeFor(note: EphemeralChatChannel): Route = Route.EphemeralChat(note.roomId.id, note.roomId.relayUrl.url)
|
||||
|
||||
fun routeFor(note: PublicChatChannel): Route = Route.PublicChatChannel(note.idHex)
|
||||
|
||||
fun routeFor(note: LiveActivitiesChannel): Route = Route.LiveActivityChannel(note.address.kind, note.address.pubKeyHex, note.address.dTag)
|
||||
|
||||
fun routeFor(roomId: RoomId): Route = Route.EphemeralChat(roomId.id, roomId.relayUrl.url)
|
||||
|
||||
fun routeFor(note: RelayGroupChannel): Route = Route.RelayGroup(note.groupId.id, note.groupId.relayUrl.url)
|
||||
|
||||
fun routeFor(groupId: GroupId): Route = Route.RelayGroup(groupId.id, groupId.relayUrl.url)
|
||||
|
||||
fun routeFor(channel: ConcordChannel): Route = Route.Concord(channel.channelId.communityId, channel.channelId.channelId)
|
||||
|
||||
fun routeFor(user: User): Route.Profile = Route.Profile(user.pubkeyHex)
|
||||
|
||||
fun routeForUser(userHex: HexKey): Route.Profile = Route.Profile(userHex)
|
||||
|
||||
fun authorRouteFor(note: Note): Route.Profile? = note.author?.pubkeyHex?.let { Route.Profile(it) }
|
||||
|
||||
fun routeReplyTo(
|
||||
note: Note,
|
||||
account: Account,
|
||||
|
||||
+2
-2
@@ -36,6 +36,8 @@ import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.your_profile_image
|
||||
import com.vitorpamplona.amethyst.commons.search.SearchQuery
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.LocalScreenLayout
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.ShorterTopAppBar
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ArrowBackIcon
|
||||
@@ -45,8 +47,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.HeaderPictureModifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size22Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.layouts.LocalScreenLayout
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
/**
|
||||
|
||||
@@ -40,6 +40,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.new_badge_award_notif
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
|
||||
@@ -36,6 +36,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import com.vitorpamplona.amethyst.commons.model.Bolt12ZapEntry
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ZappedIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size25dp
|
||||
|
||||
@@ -35,9 +35,9 @@ import com.vitorpamplona.amethyst.commons.resources.copy_text_translated
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionDialog
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionRow
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.M3ActionSection
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.components.cachedTranslation
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.ui.note.types.displayedNoteText
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -24,9 +24,9 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.SimpleImageBorder
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.BannerImage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@@ -45,7 +45,7 @@ fun DisplayAuthorBanner(
|
||||
)
|
||||
} else {
|
||||
val authorState by observeNote(baseNote, accountViewModel)
|
||||
CrossfadeIfEnabled(authorState.note.author, accountViewModel = accountViewModel) { author ->
|
||||
CrossfadeIfEnabled(authorState.note.author) { author ->
|
||||
if (author != null) {
|
||||
BannerImage(
|
||||
author,
|
||||
|
||||
@@ -36,10 +36,11 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeForUser
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size25dp
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeForUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@@ -40,6 +40,7 @@ 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.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdStartPadding
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
|
||||
@@ -72,16 +72,21 @@ import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.NoteState
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.authorRouteFor
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.nutzap
|
||||
import com.vitorpamplona.amethyst.commons.resources.profile_image
|
||||
import com.vitorpamplona.amethyst.commons.richtext.CachedRichTextParser
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.AnimatedBorderTextCornerRadius
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.FollowingIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LikedIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.RepostedIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ScoreTagSmall
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ZappedIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.InLineIconRenderer
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.HalfTopPadding
|
||||
@@ -104,10 +109,7 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUse
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.components.CoreSecretMessage
|
||||
import com.vitorpamplona.amethyst.ui.components.ExpandableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.authorRouteFor
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
+5
-4
@@ -49,6 +49,8 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeForUser
|
||||
import com.vitorpamplona.amethyst.commons.model.nip05DnsIdentifiers.Nip05State
|
||||
import com.vitorpamplona.amethyst.commons.model.nip05DnsIdentifiers.Nip05VerifState
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
@@ -56,7 +58,9 @@ 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.ui.components.ClickableTextPrimary
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Font14SP
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.NIP05IconSize
|
||||
@@ -69,11 +73,8 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserStatuses
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeForUser
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||
@@ -466,7 +467,7 @@ fun RenderNIP05VerifiedSymbol(
|
||||
modifier: Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
CrossfadeIfEnabled(targetState = state, accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = state) {
|
||||
when (it) {
|
||||
is Nip05VerifState.Verifying, is Nip05VerifState.NotStarted -> {
|
||||
// Treat "not started" and "in flight" as a single loading visual so the
|
||||
|
||||
@@ -66,6 +66,7 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.concord.ConcordChannel
|
||||
import com.vitorpamplona.amethyst.commons.model.isCommunityDefinition
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel
|
||||
import com.vitorpamplona.amethyst.commons.model.replyingDirectlyTo
|
||||
@@ -85,6 +86,7 @@ import com.vitorpamplona.amethyst.commons.resources.private_rumor_info_title
|
||||
import com.vitorpamplona.amethyst.commons.resources.private_rumor_mark
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.ClickableBox
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.GenericRepostLayout
|
||||
import com.vitorpamplona.amethyst.commons.ui.layouts.NoteComposeLayout
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
@@ -117,7 +119,6 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observe
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeCommunityApprovalNeedStatus
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteModifications
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeEditDraftTo
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.DisplayZapSplits
|
||||
|
||||
@@ -100,6 +100,7 @@ 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.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
@@ -109,7 +110,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SmallestBorder
|
||||
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.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeEditDraftTo
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.vitorpamplona.amethyst.commons.hashtags.Cashu
|
||||
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.NutzapEntry
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.nutzap
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
|
||||
@@ -37,6 +37,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.hashtags.Cashu
|
||||
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.nutzap
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
|
||||
@@ -41,6 +41,7 @@ import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.OnchainZapEntry
|
||||
import com.vitorpamplona.amethyst.commons.model.OnchainZapStatus
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.OnchainZappedIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.PendingClockBadge
|
||||
|
||||
@@ -152,7 +152,9 @@ import com.vitorpamplona.amethyst.commons.resources.reload_mint_title
|
||||
import com.vitorpamplona.amethyst.commons.resources.sats_to_complete
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.AnimatedBorderTextCornerRadius
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.ClickableBox
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ChangeReactionIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.CommentIcon
|
||||
@@ -167,6 +169,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.ShareIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.VoiceReplyIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ZapIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ZappedIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.InLineIconRenderer
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder
|
||||
@@ -222,13 +225,10 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNo
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteZaps
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCFinderFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.FloatingRecordingIndicator
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.MAX_VOICE_RECORD_SECONDS
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.RecordAudioBox
|
||||
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
||||
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeReplyTo
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.ShareOptionsBottomSheet
|
||||
import com.vitorpamplona.amethyst.ui.note.types.EditState
|
||||
@@ -603,7 +603,6 @@ private fun RenderShowIndividualReactionsButton(
|
||||
CrossfadeIfEnabled(
|
||||
targetState = wantsToSeeReactions.value,
|
||||
label = "RenderShowIndividualReactionsButton",
|
||||
accountViewModel = accountViewModel,
|
||||
) {
|
||||
if (it) {
|
||||
ExpandLessIcon(modifier = Size22Modifier, Res.string.close_all_reactions_to_this_post)
|
||||
@@ -1112,7 +1111,7 @@ fun LikeReaction(
|
||||
onLongClick = { nav.nav(Route.UpdateReactionType) },
|
||||
) {
|
||||
ObserveLikeIcon(baseNote, accountViewModel) { reactionType ->
|
||||
CrossfadeIfEnabled(targetState = reactionType, contentAlignment = Center, label = "LikeIcon", accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = reactionType, contentAlignment = Center, label = "LikeIcon") {
|
||||
if (reactionType != null) {
|
||||
RenderReactionType(reactionType, heartSizeModifier, iconFontSize)
|
||||
} else {
|
||||
@@ -1447,7 +1446,7 @@ fun ZapReaction(
|
||||
accountViewModel,
|
||||
zapStartingTime,
|
||||
) { zapIconState ->
|
||||
CrossfadeIfEnabled(targetState = zapIconState, label = "ZapIcon", accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = zapIconState, label = "ZapIcon") {
|
||||
if (it.wasZappedByLoggedInUser) {
|
||||
ZappedIcon(iconSizeModifier)
|
||||
} else {
|
||||
@@ -1460,7 +1459,7 @@ fun ZapReaction(
|
||||
baseNote,
|
||||
accountViewModel,
|
||||
) { zapIconState ->
|
||||
CrossfadeIfEnabled(targetState = zapIconState, label = "ZapIcon", accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = zapIconState, label = "ZapIcon") {
|
||||
if (it.wasZappedByLoggedInUser) {
|
||||
ZappedIcon(iconSizeModifier)
|
||||
} else if (it.hasPendingPaymentRequest) {
|
||||
|
||||
@@ -50,13 +50,13 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.expand_relay_list
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ShowMoreRelaysButtonBoxModifer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size17Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
@@ -130,7 +130,7 @@ private fun RenderRelaySlot(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
CrossfadeIfEnabled(targetState = relay, label = "RenderRelay", modifier = Size17Modifier, accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = relay, label = "RenderRelay", modifier = Size17Modifier) {
|
||||
if (it != null) {
|
||||
RenderRelay(it, accountViewModel, nav)
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.expand_relay_list
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_info
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.ClickableBox
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
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.LargeRelayIconModifier
|
||||
@@ -70,8 +72,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.relayIconModifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ripple24dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.warningColorOnSecondSurface
|
||||
import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -35,10 +35,12 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.and
|
||||
import com.vitorpamplona.amethyst.commons.resources.replying_to
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CreateClickableTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.lessImportantLink
|
||||
|
||||
+3
-2
@@ -72,13 +72,16 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.add
|
||||
import com.vitorpamplona.amethyst.commons.resources.new_reaction_symbol
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.AnimatedBorderTextCornerRadius
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LikedIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.InLineIconRenderer
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
||||
@@ -86,8 +89,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.util.firstFullChar
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEventAndMapNotNull
|
||||
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
|
||||
import com.vitorpamplona.amethyst.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.types.RenderEmojiPack
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@@ -110,6 +110,7 @@ import com.vitorpamplona.amethyst.commons.resources.zap_type_public_explainer
|
||||
import com.vitorpamplona.amethyst.commons.resources.zap_type_section_explainer
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.SettingsCategoryFirstModifier
|
||||
@@ -117,7 +118,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SettingsCategorySpacingModifi
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.keyBackup.getFragmentActivity
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
|
||||
@@ -34,6 +34,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.profile.ui.FollowButton
|
||||
import com.vitorpamplona.amethyst.commons.profile.ui.ListButton
|
||||
import com.vitorpamplona.amethyst.commons.profile.ui.UnfollowButton
|
||||
|
||||
@@ -21,47 +21,38 @@
|
||||
package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
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.layout.ContentScale
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.profile_image
|
||||
import com.vitorpamplona.amethyst.commons.resources.profile_image_of_user
|
||||
import com.vitorpamplona.amethyst.commons.resources.unknown_author
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.UserAvatar
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.FollowingIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ScoreTag
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Font10SP
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Font6SP
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Font8SP
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.SmallBorder
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserContactCardsScore
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||
@@ -525,84 +516,6 @@ fun ScoreTag55Preview() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ScoreTag(
|
||||
score: Int,
|
||||
size: Dp,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
if (size > 34.dp) {
|
||||
ScoreTagRegular(score, modifier)
|
||||
} else if (size > 23.dp) {
|
||||
ScoreTagSmall(score, modifier)
|
||||
} else {
|
||||
ScoreTagSmallest(score, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ScoreTagRegular(
|
||||
score: Int,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
Text(
|
||||
text = score.toString(),
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = Font10SP,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Clip,
|
||||
lineHeight = Font10SP,
|
||||
modifier =
|
||||
modifier
|
||||
.clip(SmallBorder)
|
||||
.background(Color.Black)
|
||||
.padding(horizontal = 4.dp, vertical = 0.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ScoreTagSmall(
|
||||
score: Int,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
Text(
|
||||
text = score.toString(),
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = Font8SP,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Clip,
|
||||
lineHeight = Font8SP,
|
||||
modifier =
|
||||
modifier
|
||||
.clip(SmallBorder)
|
||||
.background(Color.Black)
|
||||
.padding(horizontal = 3.dp, vertical = 0.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ScoreTagSmallest(
|
||||
score: Int,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
Text(
|
||||
text = score.toString(),
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = Font6SP,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Clip,
|
||||
lineHeight = Font6SP,
|
||||
modifier =
|
||||
modifier
|
||||
.clip(SmallBorder)
|
||||
.background(Color.Black)
|
||||
.padding(horizontal = 2.dp, vertical = 0.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WatchProfilePicture(
|
||||
baseUser: User,
|
||||
@@ -623,22 +536,17 @@ fun InnerUserPicture(
|
||||
modifier: Modifier,
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
val myImageModifier =
|
||||
remember {
|
||||
modifier.size(size).clip(shape = CircleShape)
|
||||
}
|
||||
|
||||
RobohashFallbackAsyncImage(
|
||||
robot = userHex,
|
||||
model = userPicture,
|
||||
UserAvatar(
|
||||
userHex = userHex,
|
||||
pictureUrl = userPicture,
|
||||
size = size,
|
||||
modifier = modifier,
|
||||
contentDescription =
|
||||
if (userName != null) {
|
||||
stringRes(id = Res.string.profile_image_of_user, userName)
|
||||
} else {
|
||||
stringRes(id = Res.string.profile_image)
|
||||
},
|
||||
modifier = myImageModifier,
|
||||
contentScale = ContentScale.Crop,
|
||||
loadProfilePicture = accountViewModel.settings.showProfilePictures(),
|
||||
loadRobohash = accountViewModel.settings.isNotPerformanceMode(),
|
||||
autoPlayGif =
|
||||
|
||||
@@ -23,30 +23,26 @@ package com.vitorpamplona.amethyst.ui.note
|
||||
import android.content.Context
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
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
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.PlayIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.UserNameText
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdButtonSizeModifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserNickname
|
||||
import com.vitorpamplona.amethyst.service.tts.TextToSpeechHelper
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
|
||||
@@ -91,7 +87,7 @@ fun WatchAuthorWithBlank(
|
||||
inner(noteAuthor)
|
||||
} else {
|
||||
val authorState by observeNote(baseNote, accountViewModel)
|
||||
CrossfadeIfEnabled(targetState = authorState.note.author, modifier = modifier, label = "WatchAuthorWithBlank", accountViewModel = accountViewModel) { newAuthor ->
|
||||
CrossfadeIfEnabled(targetState = authorState.note.author, modifier = modifier, label = "WatchAuthorWithBlank") { newAuthor ->
|
||||
inner(newAuthor)
|
||||
}
|
||||
}
|
||||
@@ -109,59 +105,22 @@ fun UsernameDisplay(
|
||||
val userMetadata by observeUserInfo(baseUser, accountViewModel)
|
||||
val nickname by observeUserNickname(baseUser, accountViewModel)
|
||||
|
||||
CrossfadeIfEnabled(targetState = userMetadata, modifier = weight, label = "UsernameDisplay", accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = userMetadata, modifier = weight, label = "UsernameDisplay") {
|
||||
// the account's own nickname for this user wins over the user's metadata;
|
||||
// its custom emojis resolve against the contact card's tags, not the profile's
|
||||
val petName = nickname?.petName
|
||||
val name = petName ?: it?.info?.bestName()
|
||||
if (name != null) {
|
||||
UserDisplay(name, if (petName != null) nickname?.tags else it?.tags, weight, fontWeight, textColor, textAlign)
|
||||
} else {
|
||||
NPubDisplay(baseUser, weight, fontWeight, textColor, textAlign)
|
||||
}
|
||||
UserNameText(
|
||||
user = baseUser,
|
||||
displayName = petName ?: it?.info?.bestName(),
|
||||
tags = if (petName != null) nickname?.tags else it?.tags,
|
||||
modifier = weight,
|
||||
fontWeight = fontWeight,
|
||||
textColor = textColor,
|
||||
textAlign = textAlign,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NPubDisplay(
|
||||
user: User,
|
||||
modifier: Modifier,
|
||||
fontWeight: FontWeight = FontWeight.Bold,
|
||||
textColor: Color = Color.Unspecified,
|
||||
textAlign: TextAlign? = null,
|
||||
) {
|
||||
Text(
|
||||
text = remember { user.pubkeyDisplayHex() },
|
||||
fontWeight = fontWeight,
|
||||
modifier = modifier,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = textColor,
|
||||
textAlign = textAlign,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UserDisplay(
|
||||
bestDisplayName: String,
|
||||
tags: ImmutableListOfLists<String>?,
|
||||
modifier: Modifier,
|
||||
fontWeight: FontWeight = FontWeight.Bold,
|
||||
textColor: Color = Color.Unspecified,
|
||||
textAlign: TextAlign? = null,
|
||||
) {
|
||||
CreateTextWithEmoji(
|
||||
text = bestDisplayName,
|
||||
tags = tags,
|
||||
fontWeight = fontWeight,
|
||||
textAlign = textAlign,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = modifier,
|
||||
color = textColor,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DrawPlayName(name: String) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@@ -27,10 +27,10 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteHasEvent
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderByParentFilterAssemblerSubscription
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@@ -80,7 +80,7 @@ fun WatchNoteEvent(
|
||||
} else {
|
||||
// avoid observing costs if already has an event.
|
||||
val hasEvent by observeNoteHasEvent(baseNote, accountViewModel)
|
||||
CrossfadeIfEnabled(targetState = hasEvent, accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = hasEvent) {
|
||||
if (it) {
|
||||
onNoteEventFound()
|
||||
} else {
|
||||
|
||||
@@ -99,6 +99,7 @@ import com.vitorpamplona.amethyst.commons.resources.zap_type_private
|
||||
import com.vitorpamplona.amethyst.commons.resources.zap_type_private_explainer
|
||||
import com.vitorpamplona.amethyst.commons.resources.zap_type_public
|
||||
import com.vitorpamplona.amethyst.commons.resources.zap_type_public_explainer
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.buttons.CloseButton
|
||||
@@ -113,7 +114,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage
|
||||
import com.vitorpamplona.amethyst.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.OnchainZapSendDialog
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ZappedIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
|
||||
import com.vitorpamplona.amethyst.commons.hashtags.Lightning
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.clink_lightning_offer
|
||||
import com.vitorpamplona.amethyst.commons.resources.clink_offer_amount_range
|
||||
|
||||
+2
-2
@@ -47,12 +47,12 @@ import com.vitorpamplona.amethyst.commons.resources.invoice_expired
|
||||
import com.vitorpamplona.amethyst.commons.resources.lightning_invoice
|
||||
import com.vitorpamplona.amethyst.commons.resources.pay
|
||||
import com.vitorpamplona.amethyst.commons.resources.sats
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size18Modifier
|
||||
import com.vitorpamplona.amethyst.service.lnurl.CachedLnInvoiceParser
|
||||
import com.vitorpamplona.amethyst.service.lnurl.InvoiceAmount
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.PaymentCard
|
||||
import com.vitorpamplona.amethyst.ui.components.PaymentCardAmount
|
||||
import com.vitorpamplona.amethyst.ui.components.PaymentCardDescription
|
||||
@@ -85,7 +85,7 @@ fun MayBeInvoicePreview(
|
||||
accountViewModel: AccountViewModel,
|
||||
) {
|
||||
LoadValueFromInvoice(lnbcWord = lnbcWord) { invoiceAmount ->
|
||||
CrossfadeIfEnabled(targetState = invoiceAmount, label = "MayBeInvoicePreview", accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = invoiceAmount, label = "MayBeInvoicePreview") {
|
||||
if (it != null) {
|
||||
InvoicePreview(it.invoice, it.amount, it.description, it.expiresAt, accountViewModel)
|
||||
} else {
|
||||
|
||||
+1
-1
@@ -95,10 +95,10 @@ import com.vitorpamplona.amethyst.commons.resources.location_picker_search_empty
|
||||
import com.vitorpamplona.amethyst.commons.resources.location_picker_search_hint
|
||||
import com.vitorpamplona.amethyst.commons.resources.location_picker_title
|
||||
import com.vitorpamplona.amethyst.commons.resources.location_picker_use_mine
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.service.location.ForwardGeolocation
|
||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||
import com.vitorpamplona.amethyst.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.geohashChat.label
|
||||
import com.vitorpamplona.quartz.experimental.bitchat.geohash.GeohashChannelLevel
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHash
|
||||
|
||||
+1
-3
@@ -42,12 +42,12 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import coil3.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.UrlPreviewState
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.state.produceCachedState
|
||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.VideoView
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||
import com.vitorpamplona.amethyst.ui.components.DisplayUrlWithLoadingSymbol
|
||||
import com.vitorpamplona.amethyst.ui.components.UrlPreviewCard
|
||||
@@ -217,7 +217,6 @@ private fun MyLoadUrlPreviewDirect(
|
||||
CrossfadeIfEnabled(
|
||||
targetState = urlPreviewState,
|
||||
label = "UrlPreview",
|
||||
accountViewModel = accountViewModel,
|
||||
) { state ->
|
||||
when (state) {
|
||||
is UrlPreviewState.Loaded -> {
|
||||
@@ -286,7 +285,6 @@ private fun MyLoadUrlPreviewDirectFillWidth(
|
||||
CrossfadeIfEnabled(
|
||||
targetState = urlPreviewState,
|
||||
label = "UrlPreview",
|
||||
accountViewModel = accountViewModel,
|
||||
) { state ->
|
||||
when (state) {
|
||||
is UrlPreviewState.Loaded -> {
|
||||
|
||||
@@ -26,6 +26,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.forked_from
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
|
||||
+1
-1
@@ -70,10 +70,10 @@ import com.vitorpamplona.amethyst.commons.resources.timestamp_it
|
||||
import com.vitorpamplona.amethyst.commons.resources.timestamp_pending
|
||||
import com.vitorpamplona.amethyst.commons.resources.unfollow
|
||||
import com.vitorpamplona.amethyst.commons.resources.unpin_from_profile
|
||||
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.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.ui.note.QuickActionAlertDialogOneButton
|
||||
import com.vitorpamplona.amethyst.ui.note.copyNoteTextAction
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
+1
-1
@@ -76,6 +76,7 @@ import com.vitorpamplona.amethyst.commons.resources.lightning_invoice
|
||||
import com.vitorpamplona.amethyst.commons.resources.post_anonymously
|
||||
import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind
|
||||
import com.vitorpamplona.amethyst.commons.resources.zapraiser
|
||||
import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.contentWarning.ContentSensitivityExplainer
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.creators.contentWarning.MarkAsSensitiveButton
|
||||
@@ -103,7 +104,6 @@ import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton
|
||||
import com.vitorpamplona.amethyst.ui.components.getActivity
|
||||
import com.vitorpamplona.amethyst.ui.insets.imePaddingSafe
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.Nav
|
||||
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
|
||||
@@ -80,6 +80,7 @@ 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
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_definition_available_on
|
||||
@@ -101,16 +102,16 @@ import com.vitorpamplona.amethyst.commons.resources.profile_banner
|
||||
import com.vitorpamplona.amethyst.commons.resources.profile_image
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.ClickableTextPrimary
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.LinkIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.richtext.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size16Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.BaseUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
|
||||
@@ -43,6 +43,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.LoadThumbAndThenVideoView
|
||||
|
||||
@@ -63,6 +63,7 @@ import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.accept_badge
|
||||
import com.vitorpamplona.amethyst.commons.resources.award_badge
|
||||
@@ -74,12 +75,12 @@ import com.vitorpamplona.amethyst.commons.resources.badge_awardees_label
|
||||
import com.vitorpamplona.amethyst.commons.resources.badge_untitled
|
||||
import com.vitorpamplona.amethyst.commons.resources.reject_badge
|
||||
import com.vitorpamplona.amethyst.commons.resources.unaccept_badge
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size30dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@@ -32,6 +32,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer
|
||||
|
||||
+1
@@ -33,6 +33,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.HalfVertPadding
|
||||
|
||||
@@ -46,6 +46,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.nip64Chess.ChessChallenge
|
||||
import com.vitorpamplona.amethyst.commons.nip64Chess.ui.ChessGameViewer
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
|
||||
@@ -62,6 +62,7 @@ import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.about_us
|
||||
@@ -76,6 +77,7 @@ import com.vitorpamplona.amethyst.commons.resources.preview_card_image_for
|
||||
import com.vitorpamplona.amethyst.commons.resources.profile_image
|
||||
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.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder
|
||||
@@ -93,7 +95,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.innerPostModifier
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent
|
||||
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.RichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||
|
||||
@@ -49,11 +49,11 @@ import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.AddButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RemoveButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size35Modifier
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteAndMap
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.ShowMoreButton
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.note.getGradient
|
||||
@@ -189,7 +189,7 @@ private fun EmojiListOptions(
|
||||
usersEmojiList.event?.isTaggedAddressableNote(emojiPackNote.idHex)
|
||||
}
|
||||
|
||||
CrossfadeIfEnabled(targetState = hasAddedThis, label = "EmojiListOptions", accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = hasAddedThis, label = "EmojiListOptions") {
|
||||
if (it != true) {
|
||||
AddButton(modifier = Modifier.padding(start = 3.dp)) { accountViewModel.addEmojiPack(emojiPackNote) }
|
||||
} else {
|
||||
|
||||
@@ -56,6 +56,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.preview_card_image_for
|
||||
|
||||
@@ -30,8 +30,8 @@ import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaLocalImage
|
||||
import com.vitorpamplona.amethyst.commons.richtext.MediaLocalVideo
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||
@@ -110,7 +110,7 @@ private fun ObserverAndRenderNIP95(
|
||||
mutableStateOf<BaseMediaContent?>(newContent)
|
||||
}
|
||||
|
||||
CrossfadeIfEnabled(targetState = content, accountViewModel = accountViewModel) {
|
||||
CrossfadeIfEnabled(targetState = content) {
|
||||
if (it != null) {
|
||||
SensitivityWarning(note = header, accountViewModel = accountViewModel) {
|
||||
ZoomableContentView(
|
||||
|
||||
@@ -43,6 +43,7 @@ import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.routeFor
|
||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.follow_list_item_label
|
||||
|
||||
@@ -48,6 +48,7 @@ import com.vitorpamplona.amethyst.commons.resources.copy_to_clipboard
|
||||
import com.vitorpamplona.amethyst.commons.resources.fundraiser_ends
|
||||
import com.vitorpamplona.amethyst.commons.resources.fundraiser_onchain_donation
|
||||
import com.vitorpamplona.amethyst.commons.resources.preview_card_image_for
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.CopyIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
@@ -55,7 +56,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size18Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier
|
||||
import com.vitorpamplona.amethyst.ui.components.MyAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.util.setText
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeaderBackground
|
||||
import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user