diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt index 1129c7773..9101a08fb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt @@ -75,9 +75,10 @@ class Amethyst : Application() { // App services that should be run as soon as there are subscribers to their flows val locationManager = LocationState(this, applicationIOScope) - val torManager = TorManager(this, applicationIOScope) val connManager = ConnectivityManager(this, applicationIOScope) + val torManager = TorManager(this, applicationIOScope) + // Service that will run at all times to receive events from Pokey val pokeyReceiver = PokeyReceiver() @@ -109,7 +110,7 @@ class Amethyst : Application() { val client: NostrClient = NostrClient(websocketBuilder, applicationIOScope) // Watches for changes on Tor and Relay List Settings - val relayProxyClientConnector = RelayProxyClientConnector(torProxySettingsAnchor, okHttpClients, connManager, client, applicationIOScope) + val relayProxyClientConnector = RelayProxyClientConnector(torProxySettingsAnchor, okHttpClients, connManager, client, torManager, applicationIOScope) // Verifies and inserts in the cache from all relays, all subscriptions val cacheClientConnector = CacheClientConnector(client, cache) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 359666aff..e14358b34 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -160,7 +160,6 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NPub import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay import com.vitorpamplona.quartz.nip19Bech32.entities.NSec import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarning -import com.vitorpamplona.quartz.nip37Drafts.DraftBuilder import com.vitorpamplona.quartz.nip37Drafts.DraftEventCache import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent @@ -185,9 +184,9 @@ import com.vitorpamplona.quartz.nip65RelayList.tags.AdvertisedRelayInfo import com.vitorpamplona.quartz.nip68Picture.PictureEvent import com.vitorpamplona.quartz.nip68Picture.PictureMeta import com.vitorpamplona.quartz.nip68Picture.pictureIMeta -import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent import com.vitorpamplona.quartz.nip71Video.VideoMeta -import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent +import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent +import com.vitorpamplona.quartz.nip71Video.VideoShortEvent import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryRequestEvent import com.vitorpamplona.quartz.nip92IMeta.IMetaTag import com.vitorpamplona.quartz.nip92IMeta.imetas @@ -215,8 +214,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.math.BigDecimal import java.util.Locale -import kotlin.collections.forEach -import kotlin.collections.ifEmpty @OptIn(DelicateCoroutinesApi::class) @Stable @@ -985,7 +982,7 @@ class Account( mimeType: String?, hash: String, duration: Int, - waveform: List, + waveform: List, ) { signAndComputeBroadcast(VoiceEvent.build(url, mimeType, hash, duration, waveform)) } @@ -995,7 +992,7 @@ class Account( mimeType: String?, hash: String, duration: Int, - waveform: List, + waveform: List, replyTo: EventHintBundle, ) { signAndComputeBroadcast(VoiceReplyEvent.build(url, mimeType, hash, duration, waveform, replyTo)) @@ -1088,11 +1085,11 @@ class Account( ) if (headerInfo.dim.height > headerInfo.dim.width) { - VideoVerticalEvent.build(videoMeta, alt ?: "") { + VideoShortEvent.build(videoMeta, alt ?: "") { contentWarningReason?.let { contentWarning(contentWarningReason) } } } else { - VideoHorizontalEvent.build(videoMeta, alt ?: "") { + VideoNormalEvent.build(videoMeta, alt ?: "") { contentWarningReason?.let { contentWarning(contentWarningReason) } } } @@ -1169,7 +1166,7 @@ class Account( val extraRelays = cache.getAddressableNoteIfExists(DraftWrapEvent.createAddressTag(signer.pubKey, draftTag))?.relays ?: emptyList() val rumor = RumorAssembler.assembleRumor(signer.pubKey, template) - val draftEvent = DraftBuilder.encryptAndSign(draftTag, rumor, signer) + val draftEvent = DraftWrapEvent.create(draftTag, rumor, signer) draftsDecryptionCache.preload(draftEvent, rumor) cache.justConsumeMyOwnEvent(draftEvent) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 085d49f27..cb9be0279 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -161,6 +161,8 @@ import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import com.vitorpamplona.quartz.nip68Picture.PictureEvent import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent +import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent +import com.vitorpamplona.quartz.nip71Video.VideoShortEvent import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent @@ -852,7 +854,7 @@ object LocalCache : ILocalCache { val creator = event.host()?.let { checkGetOrCreateUser(it.pubKey) } ?: author - channel.updateChannelInfo(creator, event) + channel.updateChannelInfo(creator, event, note) refreshNewNoteObservers(note) @@ -1036,6 +1038,18 @@ object LocalCache : ILocalCache { wasVerified: Boolean, ) = consumeBaseReplaceable(event, relay, wasVerified) + private fun consume( + event: VideoNormalEvent, + relay: NormalizedRelayUrl?, + wasVerified: Boolean, + ) = consumeBaseReplaceable(event, relay, wasVerified) + + private fun consume( + event: VideoShortEvent, + relay: NormalizedRelayUrl?, + wasVerified: Boolean, + ) = consumeBaseReplaceable(event, relay, wasVerified) + fun consume( event: StatusEvent, relay: NormalizedRelayUrl?, @@ -1548,7 +1562,7 @@ object LocalCache : ILocalCache { if (oldChannel.creator == null || oldChannel.creator == author) { if (isVerified || justVerify(event)) { - oldChannel.updateChannelInfo(author, event) + oldChannel.updateChannelInfo(author, event, note) } } @@ -1567,6 +1581,8 @@ object LocalCache : ILocalCache { val oldChannel = checkGetOrCreatePublicChatChannel(channelId) ?: return false val author = getOrCreateUser(event.pubKey) + val note = getOrCreateNote(event.id) + val isVerified = if (event.createdAt > oldChannel.updatedMetadataAt) { if (wasVerified || justVerify(event)) { @@ -1579,7 +1595,6 @@ object LocalCache : ILocalCache { wasVerified } - val note = getOrCreateNote(event.id) if (note.event == null && (isVerified || justVerify(event))) { oldChannel.addNote(note, relay) note.loadEvent(event, author, emptyList()) @@ -2821,7 +2836,9 @@ object LocalCache : ILocalCache { is TorrentCommentEvent -> consume(event, relay, wasVerified) is TrustedRelayListEvent -> consume(event, relay, wasVerified) is VideoHorizontalEvent -> consume(event, relay, wasVerified) + is VideoNormalEvent -> consume(event, relay, wasVerified) is VideoVerticalEvent -> consume(event, relay, wasVerified) + is VideoShortEvent -> consume(event, relay, wasVerified) is VoiceEvent -> consume(event, relay, wasVerified) is VoiceReplyEvent -> consume(event, relay, wasVerified) is WikiNoteEvent -> consume(event, relay, wasVerified) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 883dcb8ca..4d59a2efc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -201,16 +201,19 @@ open class Note( } fun relayHintUrl(): NormalizedRelayUrl? { - val authorRelay = author?.latestMetadataRelay + val currentOutbox = author?.outboxRelays()?.toSet() return if (relays.isNotEmpty()) { - if (authorRelay != null && relays.any { it == authorRelay }) { - authorRelay - } else { - relays.firstOrNull() + if (currentOutbox != null && currentOutbox.isNotEmpty()) { + val relayMatchesOutbox = relays.firstOrNull { it in currentOutbox } + if (relayMatchesOutbox != null) { + return relayMatchesOutbox + } } + + return relays.firstOrNull() } else { - null + currentOutbox?.firstOrNull() ?: author?.latestMetadataRelay } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip28PublicChats/PublicChatChannel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip28PublicChats/PublicChatChannel.kt index e7ea766eb..cb77d53c8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip28PublicChats/PublicChatChannel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip28PublicChats/PublicChatChannel.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.model.nip28PublicChats import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.model.Channel +import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.note.toShortDisplay import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle @@ -39,6 +40,7 @@ class PublicChatChannel( ) : Channel() { var creator: User? = null var event: ChannelCreateEvent? = null + var eventNote: Note? = null var info = ChannelDataNorm(null, null, null, null) var infoTags = EmptyTagList @@ -61,13 +63,15 @@ class PublicChatChannel( fun updateChannelInfo( creator: User, event: ChannelCreateEvent, + eventNote: Note? = null, ) { this.creator = creator this.event = event - this.info = event.channelInfo() + this.infoTags = event.tags.toImmutableListOfLists() this.updatedMetadataAt = event.createdAt + this.eventNote = eventNote updateChannelInfo() } @@ -77,8 +81,8 @@ class PublicChatChannel( event: ChannelMetadataEvent, ) { this.creator = creator - this.info = event.channelInfo() + this.infoTags = event.tags.toImmutableListOfLists() this.updatedMetadataAt = event.createdAt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip53LiveActivities/LiveActivitiesChannel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip53LiveActivities/LiveActivitiesChannel.kt index f846613e9..800ea64c5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip53LiveActivities/LiveActivitiesChannel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip53LiveActivities/LiveActivitiesChannel.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.model.nip53LiveActivities import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.model.Channel +import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.note.toShortDisplay import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag @@ -35,6 +36,7 @@ class LiveActivitiesChannel( ) : Channel() { var creator: User? = null var info: LiveActivitiesEvent? = null + var infoNote: Note? = null fun address() = address @@ -47,9 +49,11 @@ class LiveActivitiesChannel( fun updateChannelInfo( creator: User, channelInfo: LiveActivitiesEvent, + channelInfoNote: Note, ) { this.info = channelInfo this.creator = creator + this.infoNote = channelInfoNote super.updateChannelInfo() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/connectivity/ConnectivityFlow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/connectivity/ConnectivityFlow.kt index ff69703b4..978a23425 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/connectivity/ConnectivityFlow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/connectivity/ConnectivityFlow.kt @@ -63,6 +63,12 @@ class ConnectivityFlow( Log.d("ConnectivityFlow", "onCapabilitiesChanged ${network.networkHandle} $isMobile") trySend(ConnectivityStatus.Active(network.networkHandle, isMobile)) } + + override fun onLost(network: Network) { + super.onLost(network) + Log.d("ConnectivityFlow", "onLost ${network.networkHandle} ") + trySend(ConnectivityStatus.Off) + } } connectivityManager.registerDefaultNetworkCallback(networkCallback) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/VideoView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/VideoView.kt index 8b31f9892..ea0b3dd99 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/VideoView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/VideoView.kt @@ -47,7 +47,7 @@ import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag @Immutable class WaveformData( - val wave: List, + val wave: List, ) @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/RelayProxyClientConnector.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/RelayProxyClientConnector.kt index fdc54672d..a9bcf18dd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/RelayProxyClientConnector.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/RelayProxyClientConnector.kt @@ -21,14 +21,20 @@ package com.vitorpamplona.amethyst.service.relayClient import android.util.Log +import com.vitorpamplona.amethyst.model.torState.TorRelayEvaluation import com.vitorpamplona.amethyst.service.connectivity.ConnectivityManager +import com.vitorpamplona.amethyst.service.connectivity.ConnectivityStatus import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManager import com.vitorpamplona.amethyst.service.okhttp.ProxySettingsAnchor +import com.vitorpamplona.amethyst.ui.tor.TorManager +import com.vitorpamplona.amethyst.ui.tor.TorServiceStatus import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.NonCancellable.isActive import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flowOn @@ -36,14 +42,24 @@ import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import net.freehaven.tor.control.TorControlCommands +import okhttp3.OkHttpClient class RelayProxyClientConnector( val torProxySettingsAnchor: ProxySettingsAnchor, val okHttpClients: DualHttpClientManager, val connManager: ConnectivityManager, val client: NostrClient, + val torManager: TorManager, val scope: CoroutineScope, ) { + data class RelayServiceInfra( + val torSettings: StateFlow, + val torConnection: OkHttpClient, + val clearConnection: OkHttpClient, + val connectivity: ConnectivityStatus, + ) + @OptIn(FlowPreview::class) val relayServices = combine( @@ -52,11 +68,35 @@ class RelayProxyClientConnector( okHttpClients.defaultHttpClientWithoutProxy, connManager.status, ) { torSettings, torConnection, clearConnection, connectivity -> - torSettings.hashCode() + torConnection.hashCode() + clearConnection.hashCode() + connectivity.hashCode() + RelayServiceInfra(torSettings, torConnection, clearConnection, connectivity) }.debounce(100) .onEach { - Log.d("ManageRelayServices", "Relay Services have changed") - client.reconnect(true) + if (it.connectivity is ConnectivityStatus.Off) { + Log.d("ManageRelayServices", "Pausing Relay Services ${it.connectivity}") + if (client.isActive()) { + client.disconnect() + } + val torStatus = torManager.status.value + if (torStatus is TorServiceStatus.Active) { + torStatus.torControlConnection.signal(TorControlCommands.SIGNAL_DORMANT) + Log.d("ManageRelayServices", "Pausing Tor Activity") + } + } else if (it.connectivity is ConnectivityStatus.Active && !client.isActive()) { + Log.d("ManageRelayServices", "Resuming Relay Services") + + val torStatus = torManager.status.value + if (torStatus is TorServiceStatus.Active) { + torStatus.torControlConnection.signal(TorControlCommands.SIGNAL_ACTIVE) + torStatus.torControlConnection.signal(TorControlCommands.SIGNAL_NEWNYM) + Log.d("ManageRelayServices", "Resuming Tor Activity with new nym") + } + + // only calls this if the client is not active. Otherwise goes to the else below + client.connect() + } else { + Log.d("ManageRelayServices", "Relay Services have changed, reconnecting relays that need to") + client.reconnect(true) + } }.onStart { Log.d("ManageRelayServices", "Resuming Relay Services") client.connect() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt index 6d8b8c1f5..0fa2fd845 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt @@ -61,7 +61,7 @@ fun NewUserMetadataScreen( postViewModel.init(accountViewModel) val context = LocalContext.current - LaunchedEffect(accountViewModel) { + LaunchedEffect(postViewModel, accountViewModel) { postViewModel.load() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/VoiceMessageRecorder.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/VoiceMessageRecorder.kt index ba98a19c4..5c5115eae 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/VoiceMessageRecorder.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/VoiceMessageRecorder.kt @@ -35,7 +35,7 @@ import java.io.File class RecordingResult( val file: File, val mimeType: String, - val amplitudes: List, + val amplitudes: List, val duration: Int, ) @@ -44,7 +44,7 @@ class VoiceMessageRecorder { private var outputFile: File? = null private var startTime: Long = 0 private var job: Job? = null - private var amplitudes: MutableList = mutableListOf() + private var amplitudes: MutableList = mutableListOf() private fun createRecorder(context: Context): MediaRecorder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { @@ -65,7 +65,9 @@ class VoiceMessageRecorder { this.amplitudes.clear() createRecorder(context).apply { - setAudioSource(MediaRecorder.AudioSource.MIC) + setAudioEncodingBitRate(16 * 44100) + setAudioSamplingRate(44100) // Set the desired audio sampling rate (e.g., 44.1 kHz) + setAudioSource(MediaRecorder.AudioSource.VOICE_RECOGNITION) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setAudioEncoder(MediaRecorder.AudioEncoder.AAC) setOutputFile(outputFile) @@ -80,7 +82,7 @@ class VoiceMessageRecorder { job = scope.launch { while (recorder != null) { - amplitudes.add(((recorder?.maxAmplitude ?: 0) / 100).toInt()) + amplitudes.add(recorder?.maxAmplitude?.toFloat() ?: 0f) delay(1000) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/AudioWaveformReadOnly.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/AudioWaveformReadOnly.kt index 330dd51b2..b3f2c667e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/AudioWaveformReadOnly.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/AudioWaveformReadOnly.kt @@ -20,6 +20,8 @@ */ package com.vitorpamplona.amethyst.ui.components +import android.R.attr.maxHeight +import android.R.attr.minHeight import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween @@ -48,6 +50,7 @@ import androidx.compose.ui.unit.coerceIn import androidx.compose.ui.unit.dp import com.linc.audiowaveform.model.AmplitudeType import com.linc.audiowaveform.model.WaveformAlignment +import com.vitorpamplona.amethyst.ui.components.toDrawableAmplitudes import kotlin.math.ceil import kotlin.math.roundToInt @@ -77,7 +80,7 @@ fun AudioWaveformReadOnly( spikeRadius: Dp = 2.dp, spikePadding: Dp = 2.dp, progress: Float = 0F, - amplitudes: List, + amplitudes: List, onProgressChange: (Float) -> Unit, ) { val backgroundColor = MaterialTheme.colorScheme.background @@ -101,6 +104,7 @@ fun AudioWaveformReadOnly( maxHeight = canvasSize.height.coerceAtLeast(MIN_SPIKE_HEIGHT), ) }.map { animateFloatAsState(it, spikeAnimationSpec).value } + Canvas( modifier = Modifier @@ -145,14 +149,13 @@ fun AudioWaveformReadOnly( } } -private fun List.toDrawableAmplitudes( +private fun List.toDrawableAmplitudes( amplitudeType: AmplitudeType, spikes: Int, minHeight: Float, maxHeight: Float, ): List { - val amplitudes = map(Int::toFloat) - if (amplitudes.isEmpty() || spikes == 0) { + if (this.isEmpty() || spikes == 0) { return List(spikes) { minHeight } } val transform = { data: List -> @@ -161,11 +164,10 @@ private fun List.toDrawableAmplitudes( AmplitudeType.Max -> data.max() AmplitudeType.Min -> data.min() }.toFloat() - .coerceIn(minHeight, maxHeight) } return when { - spikes > amplitudes.count() -> amplitudes.fillToSize(spikes, transform) - else -> amplitudes.chunkToSize(spikes, transform) + spikes > this.count() -> this.fillToSize(spikes, transform) + else -> this.chunkToSize(spikes, transform) }.normalize(minHeight, maxHeight) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt index aab99a11a..a006bd5ab 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreviewCard.kt @@ -104,7 +104,7 @@ fun UrlPreviewCard( AsyncImage( model = previewInfo.imageUrlFullPath, contentDescription = previewInfo.title, - contentScale = ContentScale.Crop, + contentScale = ContentScale.FillWidth, modifier = previewCardImageModifier, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index 3e6cb052b..9a661c941 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -149,11 +149,32 @@ fun AppNavigation( composableFromEndArgs { ChatroomScreen(it.toKey(), it.message, it.replyId, it.draftId, accountViewModel, nav) } composableFromEndArgs { ChatroomByAuthorScreen(it.id, null, accountViewModel, nav) } - composableFromEndArgs { PublicChatChannelScreen(it.id, accountViewModel, nav) } - composableFromEndArgs { LiveActivityChannelScreen(Address(it.kind, it.pubKeyHex, it.dTag), accountViewModel, nav) } + composableFromEndArgs { + PublicChatChannelScreen( + it.id, + it.draftId?.let { hex -> accountViewModel.getNoteIfExists(hex) }, + accountViewModel, + nav, + ) + } + + composableFromEndArgs { + LiveActivityChannelScreen( + Address(it.kind, it.pubKeyHex, it.dTag), + draft = it.draftId?.let { hex -> accountViewModel.getNoteIfExists(hex) }, + accountViewModel, + nav, + ) + } + composableFromEndArgs { RelayUrlNormalizer.normalizeOrNull(it.relayUrl)?.let { relay -> - EphemeralChatScreen(RoomId(it.id, relay), accountViewModel, nav) + EphemeralChatScreen( + channelId = RoomId(it.id, relay), + draft = it.draftId?.let { hex -> accountViewModel.getNoteIfExists(hex) }, + accountViewModel = accountViewModel, + nav = nav, + ) } } @@ -180,6 +201,7 @@ fun AppNavigation( NewPublicMessageScreen( to = it.toKey(), reply = it.replyId?.let { hex -> accountViewModel.getNoteIfExists(hex) }, + draft = it.draftId?.let { hex -> accountViewModel.getNoteIfExists(hex) }, accountViewModel = accountViewModel, nav = nav, ) @@ -221,7 +243,7 @@ fun AppNavigation( ) } - composableFromBottomArgs { + composableFromBottomArgs { ShortNotePostScreen( message = it.message, attachment = it.attachment?.ifBlank { null }?.toUri(), @@ -262,7 +284,7 @@ private fun NavigateIfIntentRequested( if (activity.intent.action == Intent.ACTION_SEND) { // avoids restarting the new Post screen when the intent is for the screen. // Microsoft's swift key sends Gifs as new actions - if (isBaseRoute(nav.controller)) return + if (isBaseRoute(nav.controller)) return // saves the intent to avoid processing again var message by remember { @@ -279,7 +301,7 @@ private fun NavigateIfIntentRequested( ) } - nav.newStack(Route.NewPost(message = message, attachment = media.toString())) + nav.newStack(Route.NewShortNote(message = message, attachment = media.toString())) } else { var newAccount by remember { mutableStateOf(null) } @@ -338,13 +360,13 @@ private fun NavigateIfIntentRequested( if (intent.action == Intent.ACTION_SEND) { // avoids restarting the new Post screen when the intent is for the screen. // Microsoft's swift key sends Gifs as new actions - if (!isBaseRoute(nav.controller)) { + if (!isBaseRoute(nav.controller)) { intent.getStringExtra(Intent.EXTRA_TEXT)?.let { - nav.newStack(Route.NewPost(message = it)) + nav.newStack(Route.NewShortNote(message = it)) } (intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { - nav.newStack(Route.NewPost(attachment = it.toString())) + nav.newStack(Route.NewShortNote(attachment = it.toString())) } } } else { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/RouteMaker.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/RouteMaker.kt index d6df69fd7..d62bdc771 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/RouteMaker.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/RouteMaker.kt @@ -28,6 +28,7 @@ import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent @@ -40,6 +41,7 @@ import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent import com.vitorpamplona.quartz.nip28PublicChat.base.IsInPublicChatChannel +import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent @@ -49,6 +51,7 @@ import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefiniti import com.vitorpamplona.quartz.nip73ExternalIds.location.isGeohashedScoped import com.vitorpamplona.quartz.nip73ExternalIds.topics.isHashtagScoped import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent +import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent fun routeFor( note: Note, @@ -78,6 +81,10 @@ fun routeFor( innerEvent.activityAddress()?.let { return Route.LiveActivityChannel(it.kind, it.pubKeyHex, it.dTag) } + } else if (innerEvent is EphemeralChatEvent) { + innerEvent.roomId()?.let { + return Route.EphemeralChat(it.id, it.relayUrl.url) + } } else if (innerEvent is ChatroomKeyable) { val room = innerEvent.chatroomKey(loggedIn.userProfile().pubkeyHex) loggedIn.chatroomList.getOrCreatePrivateChatroom(room) @@ -206,7 +213,7 @@ fun routeReplyTo( users = noteEvent.groupKeySet() - account.userProfile().pubkeyHex, parentId = noteEvent.id, ) - is TextNoteEvent -> Route.NewPost(baseReplyTo = note.idHex) + is TextNoteEvent -> Route.NewShortNote(baseReplyTo = note.idHex) is PrivateDmEvent -> routeToMessage( room = noteEvent.chatroomKey(account.userProfile().pubkeyHex), @@ -236,3 +243,49 @@ fun routeReplyTo( else -> Route.GenericCommentPost(replyTo = note.idHex) } } + +suspend fun routeEditDraftTo( + note: Note, + account: Account, +): Route? { + val noteEvent = note.event as DraftWrapEvent + val draft = account.draftsDecryptionCache.cachedDraft(noteEvent) + + return when (draft) { + is ChannelMessageEvent -> draft.channelId()?.let { Route.PublicChatChannel(it, draftId = note.idHex) } + is LiveActivitiesChatMessageEvent -> { + draft.activityAddress()?.let { Route.LiveActivityChannel(it.kind, it.pubKeyHex, it.dTag, draftId = note.idHex) } + } + is EphemeralChatEvent -> { + draft.roomId()?.let { Route.EphemeralChat(it.id, it.relayUrl.url, draftId = note.idHex) } + } + + is ChatroomKeyable -> { + val room = draft.chatroomKey(account.userProfile().pubkeyHex) + account.chatroomList.getOrCreatePrivateChatroom(room) + return Route.Room(room, draftId = note.idHex) + } + + is TextNoteEvent -> Route.NewShortNote(draft = note.idHex) + is ClassifiedsEvent -> Route.NewProduct(draft = note.idHex) + + is PublicMessageEvent -> + Route.NewPublicMessage( + users = draft.groupKeySet() - account.userProfile().pubkeyHex, + parentId = noteEvent.id, + draftId = note.idHex, + ) + + is CommentEvent -> { + if (draft.isGeohashedScoped()) { + Route.GeoPost(draft = note.idHex) + } else if (draft.isHashtagScoped()) { + Route.HashtagPost(draft = note.idHex) + } else { + Route.GenericCommentPost(draft = note.idHex) + } + } + + else -> null + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt index f7fd7ea07..5a205a6e5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/routes/Routes.kt @@ -92,16 +92,14 @@ sealed class Route { @Serializable data class PublicChatChannel( val id: String, + val draftId: HexKey? = null, ) : Route() @Serializable data class LiveActivityChannel( val kind: Int, val pubKeyHex: HexKey, val dTag: String, - ) : Route() - - @Serializable data class EphemeralChatChannel( - val id: String, + val draftId: HexKey? = null, ) : Route() @Serializable data class RelayInfo( @@ -111,6 +109,7 @@ sealed class Route { @Serializable data class EphemeralChat( val id: String, val relayUrl: String, + val draftId: HexKey? = null, ) : Route() @Serializable object NewEphemeralChat : Route() @@ -143,10 +142,12 @@ sealed class Route { @Serializable data class NewPublicMessage( val to: String, val replyId: HexKey? = null, + val draftId: HexKey? = null, ) : Route() { - constructor(users: Set, parentId: HexKey) : this( + constructor(users: Set, parentId: HexKey, draftId: HexKey? = null) : this( to = users.joinToString(","), replyId = parentId, + draftId = draftId, ) fun toKey(): Set = to.split(",").toSet() @@ -198,7 +199,7 @@ sealed class Route { ) : Route() @Serializable - data class NewPost( + data class NewShortNote( val message: String? = null, val attachment: String? = null, val baseReplyTo: String? = null, @@ -250,7 +251,7 @@ fun getRouteWithArguments(navController: NavHostController): Route? { dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() - dest.hasRoute() -> entry.toRoute() + dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() dest.hasRoute() -> entry.toRoute() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 776d04c7f..1a99a0cb0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -208,6 +208,8 @@ import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import com.vitorpamplona.quartz.nip68Picture.PictureEvent import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent +import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent +import com.vitorpamplona.quartz.nip71Video.VideoShortEvent import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent @@ -793,6 +795,8 @@ private fun RenderNoteRow( is FileHeaderEvent -> FileHeaderDisplay(baseNote, true, ContentScale.FillWidth, accountViewModel) is VideoHorizontalEvent -> VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, ContentScale.FillWidth, accountViewModel, nav) is VideoVerticalEvent -> VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, ContentScale.FillWidth, accountViewModel, nav) + is VideoNormalEvent -> VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, ContentScale.FillWidth, accountViewModel, nav) + is VideoShortEvent -> VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, ContentScale.FillWidth, accountViewModel, nav) is PictureEvent -> PictureDisplay(baseNote, true, ContentScale.FillWidth, PaddingValues(vertical = 5.dp), backgroundColor, accountViewModel, nav) is BaseVoiceEvent -> RenderVoiceTrack(baseNote, ContentScale.FillWidth, accountViewModel, nav) is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote, true, ContentScale.FillWidth, accountViewModel) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt index 84339a6af..698f43491 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteQuickActionMenu.kt @@ -84,7 +84,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.ui.navigation.navs.INav -import com.vitorpamplona.amethyst.ui.navigation.routes.Route +import com.vitorpamplona.amethyst.ui.navigation.routes.routeEditDraftTo import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.report.ReportNoteDialog @@ -169,11 +169,9 @@ fun NoteQuickActionMenu( note = note, onDismiss = onDismiss, onWantsToEditDraft = { - nav.nav( - Route.NewPost( - draft = note.idHex, - ), - ) + nav.nav { + routeEditDraftTo(note, accountViewModel.account) + } }, accountViewModel = accountViewModel, nav = nav, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index aa410cad6..24a9b4f15 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -48,7 +48,6 @@ import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CardDefaults @@ -155,7 +154,6 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.reactionBox import com.vitorpamplona.amethyst.ui.theme.ripple24dp import com.vitorpamplona.amethyst.ui.theme.selectedReactionBoxModifier -import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.EmptyClientListener.onError import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji @@ -541,7 +539,7 @@ private fun BoostWithDialog( accountViewModel, onQuotePress = { nav.nav { - Route.NewPost( + Route.NewShortNote( quote = baseNote.idHex, version = (editState.value as? GenericLoadable.Loaded) @@ -562,7 +560,7 @@ private fun BoostWithDialog( null } - Route.NewPost( + Route.NewShortNote( baseReplyTo = replyTo?.idHex, fork = baseNote.idHex, version = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt index ef1a11556..5ff25dc23 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DropDownMenu.kt @@ -47,7 +47,7 @@ import com.vitorpamplona.amethyst.ui.actions.EditPostView import com.vitorpamplona.amethyst.ui.components.ClickableBox import com.vitorpamplona.amethyst.ui.components.GenericLoadable import com.vitorpamplona.amethyst.ui.navigation.navs.INav -import com.vitorpamplona.amethyst.ui.navigation.routes.Route +import com.vitorpamplona.amethyst.ui.navigation.routes.routeEditDraftTo import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon import com.vitorpamplona.amethyst.ui.note.externalLinkForNote import com.vitorpamplona.amethyst.ui.note.types.EditState @@ -240,11 +240,9 @@ fun NoteDropDownMenu( DropdownMenuItem( text = { Text(stringRes(R.string.edit_draft)) }, onClick = { - val route = - Route.NewPost( - draft = note.idHex, - ) - nav.nav(route) + nav.nav { + routeEditDraftTo(note, accountViewModel.account) + } }, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt index d05efddce..d5874e824 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt @@ -104,7 +104,7 @@ fun ReplyCommentPostScreen( val context = LocalContext.current - LaunchedEffect(Unit) { + LaunchedEffect(postViewModel, accountViewModel) { reply?.let { postViewModel.reply(it) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index f1d17eff5..fa3fa2a02 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -1691,7 +1691,7 @@ class AccountViewModel( class CachedDraftNotes( val accountViewModel: AccountViewModel, - ) : GenericBaseCacheAsync(20) { + ) : GenericBaseCacheAsync(100) { override suspend fun compute(key: DraftWrapEvent): Note? = withContext(Dispatchers.IO) { val decrypted = accountViewModel.account.draftsDecryptionCache.cachedDraft(key) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt index e5c919f14..31e72b622 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DecryptAndIndexProcessor.kt @@ -199,7 +199,7 @@ class DraftEventHandler( eventNote: Note, publicNote: Note, ) { - if (event.pubKey == account.signer.pubKey && !event.isDeleted()) { + if (event.pubKey == account.signer.pubKey && !event.isDeleted() && !LocalCache.deletionIndex.hasBeenDeleted(event)) { val rumor = account.draftsDecryptionCache.preCachedDraft(event) ?: account.draftsDecryptionCache.cachedDraft(event) rumor?.let { indexDraftAsRealEvent(eventNote, it) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt index b5e831e9b..3aeabc0fc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoggedInPage.kt @@ -138,7 +138,6 @@ fun ObserveImageLoadingTor(accountViewModel: AccountViewModel) { fun ManageRelayServices(accountViewModel: AccountViewModel) { val relayServices by Amethyst.instance.relayProxyClientConnector.relayServices .collectAsStateWithLifecycle() - Log.d("ManageRelayServices", "Relay Services changed $relayServices") } @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/ChatroomScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/ChatroomScreen.kt index 28a4156c4..4b709f1eb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/ChatroomScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/ChatroomScreen.kt @@ -21,7 +21,9 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue @@ -49,7 +51,7 @@ fun ChatroomScreen( }, accountViewModel = accountViewModel, ) { - Column(Modifier.padding(it)) { + Column(Modifier.padding(it).consumeWindowInsets(it).statusBarsPadding()) { ChatroomView( room = roomId, draftMessage = draftMessage, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/ChatroomView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/ChatroomView.kt index ec40203d9..214684a90 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/ChatroomView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/ChatroomView.kt @@ -70,7 +70,7 @@ fun ChatroomView( newPostModel.load(room) if (replyToNote != null) { - LaunchedEffect(key1 = replyToNote) { + LaunchedEffect(key1 = replyToNote, newPostModel, accountViewModel) { accountViewModel.checkGetOrCreateNote(replyToNote) { if (it != null) { newPostModel.reply(it) @@ -79,7 +79,7 @@ fun ChatroomView( } } if (editFromDraft != null) { - LaunchedEffect(key1 = replyToNote) { + LaunchedEffect(replyToNote, newPostModel, accountViewModel) { accountViewModel.checkGetOrCreateNote(editFromDraft) { if (it != null) { newPostModel.editFromDraft(it) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt index 4d785f670..94f18b6a6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt @@ -151,12 +151,12 @@ fun NewGroupDMScreen( val context = LocalContext.current - LaunchedEffect(Unit) { - withContext(Dispatchers.IO) { - message?.ifBlank { null }?.let { - postViewModel.updateMessage(TextFieldValue(it)) - } - attachment?.let { + LaunchedEffect(postViewModel, accountViewModel) { + message?.ifBlank { null }?.let { + postViewModel.updateMessage(TextFieldValue(it)) + } + attachment?.let { + withContext(Dispatchers.IO) { val mediaType = context.contentResolver.getType(it) postViewModel.pickedMedia(persistentListOf(SelectedMedia(it, mediaType))) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/ephemChat/ChannelView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/ephemChat/ChannelView.kt index daa967c5d..dcce16c5c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/ephemChat/ChannelView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/ephemChat/ChannelView.kt @@ -25,10 +25,12 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.ui.navigation.navs.INav @@ -44,6 +46,7 @@ import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId @Composable fun EphemeralChatChannelView( channelId: RoomId?, + draft: Note? = null, accountViewModel: AccountViewModel, nav: INav, ) { @@ -52,6 +55,7 @@ fun EphemeralChatChannelView( LoadEphemeralChatChannel(channelId, accountViewModel) { ephem -> PrepareChannelViewModels( baseChannel = ephem, + draft = draft, accountViewModel = accountViewModel, nav = nav, ) @@ -61,6 +65,7 @@ fun EphemeralChatChannelView( @Composable private fun PrepareChannelViewModels( baseChannel: EphemeralChatChannel, + draft: Note? = null, accountViewModel: AccountViewModel, nav: INav, ) { @@ -78,6 +83,12 @@ private fun PrepareChannelViewModels( channelScreenModel.init(accountViewModel) channelScreenModel.load(baseChannel) + if (draft != null) { + LaunchedEffect(draft, channelScreenModel, accountViewModel) { + channelScreenModel.editFromDraft(draft) + } + } + ChannelView( channel = baseChannel, feedViewModel = feedViewModel, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/ephemChat/EphemeralChatScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/ephemChat/EphemeralChatScreen.kt index a281a077f..49144b988 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/ephemChat/EphemeralChatScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/ephemChat/EphemeralChatScreen.kt @@ -22,8 +22,10 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephem import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -33,6 +35,7 @@ import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId @Composable fun EphemeralChatScreen( channelId: RoomId, + draft: Note? = null, accountViewModel: AccountViewModel, nav: INav, ) { @@ -45,8 +48,8 @@ fun EphemeralChatScreen( }, accountViewModel = accountViewModel, ) { - Column(Modifier.padding(it)) { - EphemeralChatChannelView(channelId, accountViewModel, nav) + Column(Modifier.padding(it).statusBarsPadding()) { + EphemeralChatChannelView(channelId, draft, accountViewModel, nav) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/ChannelView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/ChannelView.kt index 896c357a1..aa0371fdc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/ChannelView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/ChannelView.kt @@ -25,10 +25,12 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.ui.navigation.navs.INav @@ -44,6 +46,7 @@ import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer @Composable fun PublicChatChannelView( channelId: String?, + draft: Note? = null, accountViewModel: AccountViewModel, nav: INav, ) { @@ -52,6 +55,7 @@ fun PublicChatChannelView( LoadPublicChatChannel(channelId, accountViewModel) { PrepareChannelViewModels( baseChannel = it, + draft = draft, accountViewModel = accountViewModel, nav = nav, ) @@ -61,6 +65,7 @@ fun PublicChatChannelView( @Composable fun PrepareChannelViewModels( baseChannel: PublicChatChannel, + draft: Note? = null, accountViewModel: AccountViewModel, nav: INav, ) { @@ -78,6 +83,12 @@ fun PrepareChannelViewModels( channelScreenModel.init(accountViewModel) channelScreenModel.load(baseChannel) + if (draft != null) { + LaunchedEffect(draft, channelScreenModel, accountViewModel) { + channelScreenModel.editFromDraft(draft) + } + } + ChannelView( channel = baseChannel, feedViewModel = feedViewModel, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/PublicChatChannelScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/PublicChatChannelScreen.kt index 8c7cd6b93..1a08e226f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/PublicChatChannelScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/PublicChatChannelScreen.kt @@ -22,8 +22,10 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28 import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.note.LoadPublicChatChannel @@ -34,6 +36,7 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey @Composable fun PublicChatChannelScreen( channelId: HexKey?, + draft: Note?, accountViewModel: AccountViewModel, nav: INav, ) { @@ -48,8 +51,8 @@ fun PublicChatChannelScreen( }, accountViewModel = accountViewModel, ) { - Column(Modifier.padding(it)) { - PublicChatChannelView(channelId, accountViewModel, nav) + Column(Modifier.padding(it).statusBarsPadding()) { + PublicChatChannelView(channelId, draft, accountViewModel, nav) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/ChannelView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/ChannelView.kt index d986e092c..2e09c9d64 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/ChannelView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/ChannelView.kt @@ -25,10 +25,12 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.ui.navigation.navs.INav @@ -45,6 +47,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address @Composable fun LiveActivityChannelView( channelId: Address?, + draft: Note? = null, accountViewModel: AccountViewModel, nav: INav, ) { @@ -53,6 +56,7 @@ fun LiveActivityChannelView( LoadLiveActivityChannel(channelId, accountViewModel) { PrepareChannelViewModels( baseChannel = it, + draft = draft, accountViewModel = accountViewModel, nav = nav, ) @@ -62,6 +66,7 @@ fun LiveActivityChannelView( @Composable fun PrepareChannelViewModels( baseChannel: LiveActivitiesChannel, + draft: Note? = null, accountViewModel: AccountViewModel, nav: INav, ) { @@ -79,6 +84,12 @@ fun PrepareChannelViewModels( channelScreenModel.init(accountViewModel) channelScreenModel.load(baseChannel) + if (draft != null) { + LaunchedEffect(draft, channelScreenModel, accountViewModel) { + channelScreenModel.editFromDraft(draft) + } + } + LiveActivityChannelView( channel = baseChannel, feedViewModel = feedViewModel, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/LiveActivityChannelScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/LiveActivityChannelScreen.kt index 4136ed19d..f078f7ece 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/LiveActivityChannelScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/LiveActivityChannelScreen.kt @@ -22,8 +22,10 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53 import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.note.LoadLiveActivityChannel @@ -34,6 +36,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address @Composable fun LiveActivityChannelScreen( channelId: Address?, + draft: Note? = null, accountViewModel: AccountViewModel, nav: INav, ) { @@ -48,8 +51,8 @@ fun LiveActivityChannelScreen( }, accountViewModel = accountViewModel, ) { - Column(Modifier.padding(it)) { - LiveActivityChannelView(channelId, accountViewModel, nav) + Column(Modifier.padding(it).statusBarsPadding()) { + LiveActivityChannelView(channelId, draft, accountViewModel, nav) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt index f63406ba2..61fb907dd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt @@ -207,10 +207,10 @@ open class ChannelNewMessageViewModel : private fun loadFromDraft(draft: Note) { val draftEvent = draft.event ?: return - val localfowardZapTo = draftEvent.tags.zapSplitSetup() - val totalWeight = localfowardZapTo.sumOf { it.weight } + val localForwardZapTo = draftEvent.tags.zapSplitSetup() + val totalWeight = localForwardZapTo.sumOf { it.weight } forwardZapTo = SplitBuilder() - localfowardZapTo.forEach { + localForwardZapTo.forEach { if (it is ZapSplitSetup) { val user = LocalCache.getOrCreateUser(it.pubKeyHex) forwardZapTo.addItem(user, (it.weight / totalWeight).toFloat()) @@ -218,7 +218,7 @@ open class ChannelNewMessageViewModel : // don't support edditing old-style splits. } forwardZapToEditting = TextFieldValue("") - wantsForwardZapTo = localfowardZapTo.isNotEmpty() + wantsForwardZapTo = localForwardZapTo.isNotEmpty() wantsToMarkAsSensitive = draftEvent.isSensitive() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/FilterFollowingPublicChats.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/FilterFollowingPublicChats.kt index e836d3d95..a085e5d76 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/FilterFollowingPublicChats.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/FilterFollowingPublicChats.kt @@ -29,7 +29,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent import com.vitorpamplona.quartz.utils.mapOfSet -fun filterFollowingPublicChats( +fun filterFollowingPublicChatsCreationEvent( followingChannels: Set, since: SincePerRelayMap?, ): List? { @@ -38,13 +38,16 @@ fun filterFollowingPublicChats( val relayRoomDTags = mapOfSet { followingChannels.forEach { channelId -> - val relays = - LocalCache.getPublicChatChannelIfExists(channelId)?.relays() - ?: LocalCache.relayHints.hintsForEvent(channelId).ifEmpty { null } - ?: Constants.eventFinderRelays + val channel = LocalCache.getPublicChatChannelIfExists(channelId) + if (channel?.event == null) { + val relays = + channel?.relays()?.ifEmpty { null } + ?: LocalCache.relayHints.hintsForEvent(channelId).ifEmpty { null } + ?: Constants.eventFinderRelays - relays.forEach { relayUrl -> - add(relayUrl, channelId) + relays.forEach { relayUrl -> + add(relayUrl, channelId) + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/FilterLastMessageFollowingPublicChats.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/FilterLastMessageFollowingPublicChats.kt index 0c9c83141..8b8679956 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/FilterLastMessageFollowingPublicChats.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/FilterLastMessageFollowingPublicChats.kt @@ -40,7 +40,7 @@ fun filterLastMessageFollowingPublicChats( mapOfSet { followingChannels.forEach { channelId -> val relays = - LocalCache.getPublicChatChannelIfExists(channelId)?.relays() + LocalCache.getPublicChatChannelIfExists(channelId)?.relays()?.ifEmpty { null } ?: LocalCache.relayHints.hintsForEvent(channelId).ifEmpty { null } ?: Constants.eventFinderRelays diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/FollowingPublicChatSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/FollowingPublicChatSubAssembler.kt index a59c0fdc5..4f3c10bcc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/FollowingPublicChatSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/datasource/FollowingPublicChatSubAssembler.kt @@ -43,7 +43,7 @@ class FollowingPublicChatSubAssembler( ): List? = listOfNotNull( filterLastMessageFollowingPublicChats(key.account.publicChatList.flowSet.value, since), - filterFollowingPublicChats(key.account.publicChatList.flowSet.value, since), + filterFollowingPublicChatsCreationEvent(key.account.publicChatList.flowSet.value, since), ).flatten() override fun user(key: ChatroomListState) = key.account.userProfile() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/NewCommunityNoteButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/NewCommunityNoteButton.kt index 158eab186..dcf84afab 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/NewCommunityNoteButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/NewCommunityNoteButton.kt @@ -57,7 +57,7 @@ fun NewCommunityNoteButton( FloatingActionButton( onClick = { val route = - Route.NewPost( + Route.NewShortNote( baseReplyTo = note.idHex, ) nav.nav(route) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt index 25f64e65f..9d0ec5516 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt @@ -101,7 +101,7 @@ fun NewProductScreen( val context = LocalContext.current - LaunchedEffect(Unit) { + LaunchedEffect(postViewModel, accountViewModel) { postViewModel.reloadRelaySet() draft?.let { postViewModel.editFromDraft(it) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt index 6baf3fdf3..03597a7b6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt @@ -53,7 +53,7 @@ fun GeoHashPostScreen( val context = LocalContext.current - LaunchedEffect(Unit) { + LaunchedEffect(postViewModel, accountViewModel) { geohash?.let { postViewModel.newPostFor(GeohashId(it)) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt index 59d4e8f8f..669cc05fc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt @@ -53,7 +53,7 @@ fun HashtagPostScreen( val context = LocalContext.current - LaunchedEffect(Unit) { + LaunchedEffect(postViewModel, accountViewModel) { hashtag?.let { postViewModel.newPostFor(HashtagId(it)) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/NewNoteButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/NewNoteButton.kt index cc87c14c6..6823cf817 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/NewNoteButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/NewNoteButton.kt @@ -38,7 +38,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size55Modifier fun NewNoteButton(nav: INav) { FloatingActionButton( onClick = { - nav.nav(Route.NewPost()) + nav.nav(Route.NewShortNote()) }, modifier = Size55Modifier, shape = CircleShape, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt index ec309dcfd..c9821ec97 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt @@ -99,7 +99,7 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext @OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) @Composable @@ -120,13 +120,13 @@ fun ShortNotePostScreen( val context = LocalContext.current val activity = context.getActivity() - LaunchedEffect(Unit) { - launch(Dispatchers.IO) { - postViewModel.load(baseReplyTo, quote, fork, version, draft) - message?.ifBlank { null }?.let { - postViewModel.updateMessage(TextFieldValue(it)) - } - attachment?.let { + LaunchedEffect(postViewModel, accountViewModel) { + postViewModel.load(baseReplyTo, quote, fork, version, draft) + message?.ifBlank { null }?.let { + postViewModel.updateMessage(TextFieldValue(it)) + } + attachment?.let { + withContext(Dispatchers.IO) { val mediaType = context.contentResolver.getType(it) postViewModel.selectImage(persistentListOf(SelectedMedia(it, mediaType))) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt index 49f8b1dc5..4d38c1aa5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt @@ -86,7 +86,9 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip10Notes.content.findHashtags import com.vitorpamplona.quartz.nip10Notes.content.findNostrUris import com.vitorpamplona.quartz.nip10Notes.content.findURLs +import com.vitorpamplona.quartz.nip10Notes.tags.markedETags import com.vitorpamplona.quartz.nip10Notes.tags.notify +import com.vitorpamplona.quartz.nip10Notes.tags.prepareETagsAsReplyTo import com.vitorpamplona.quartz.nip18Reposts.quotes.quotes import com.vitorpamplona.quartz.nip18Reposts.quotes.taggedQuoteIds import com.vitorpamplona.quartz.nip22Comments.CommentEvent @@ -544,12 +546,47 @@ open class ShortNotePostViewModel : imetas(usedAttachments) } } else { - TextNoteEvent.build( - note = tagger.message, - replyingTo = originalNote?.toEventHint(), - forkingFrom = forkedFromNote?.toEventHint(), - ) { - tagger.pTags?.let { pTagList -> notify(pTagList.map { it.toPTag() }) } + TextNoteEvent.build(tagger.message) { + val replyingTo = originalNote?.toEventHint() + val forkingFrom = forkedFromNote?.toEventHint() + + if (replyingTo != null || forkingFrom != null) { + val tags = prepareETagsAsReplyTo(replyingTo, forkingFrom) + // fixes wrong tags from previous clients + tags.forEach { + val note = accountViewModel.getNoteIfExists(it.eventId) + val ourAuthor = note?.author?.pubkeyHex + val ourHint = note?.relayHintUrl() + if (it.author == null || it.author?.isBlank() == true) { + it.author = ourAuthor + } else { + if (ourAuthor != null && it.author != ourAuthor) { + it.author = ourAuthor + } + } + if (it.relay == null) { + it.relay = ourHint + } else { + if (ourHint != null && it.relay != ourHint) { + it.relay = ourHint + } + } + } + markedETags(tags) + } + + tagger.pTags?.let { userList -> + val tags = + userList.map { + val tag = it.toPTag() + if (tag.relayHint == null) { + tag.copy(relayHint = LocalCache.relayHints.hintsForKey(it.pubkeyHex).firstOrNull()) + } else { + tag + } + } + notify(tags) + } hashtags(findHashtags(tagger.message)) references(findURLs(tagger.message)) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt index 29fa178c2..dd280e785 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt @@ -105,13 +105,14 @@ import kotlinx.coroutines.withContext fun NewPublicMessageScreen( to: Set? = null, reply: Note? = null, + draft: Note? = null, accountViewModel: AccountViewModel, nav: Nav, ) { val postViewModel: NewPublicMessageViewModel = viewModel() postViewModel.init(accountViewModel) - LaunchedEffect(Unit) { + LaunchedEffect(postViewModel, accountViewModel) { withContext(Dispatchers.IO) { to?.let { postViewModel.load(it) @@ -119,6 +120,9 @@ fun NewPublicMessageScreen( reply?.let { postViewModel.reply(it) } + draft?.let { + postViewModel.editFromDraft(it) + } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/datasource/FilterUserProfileMedia.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/datasource/FilterUserProfileMedia.kt index 869640bda..96e2b5a89 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/datasource/FilterUserProfileMedia.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/datasource/FilterUserProfileMedia.kt @@ -28,6 +28,8 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip68Picture.PictureEvent import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent +import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent +import com.vitorpamplona.quartz.nip71Video.VideoShortEvent import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent val UserProfileMediaKinds = @@ -36,6 +38,8 @@ val UserProfileMediaKinds = ProfileGalleryEntryEvent.KIND, VideoVerticalEvent.KIND, VideoHorizontalEvent.KIND, + VideoNormalEvent.KIND, + VideoShortEvent.KIND, ) fun filterUserProfileMedia( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt index 3f8df123f..20038d00d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt @@ -59,7 +59,7 @@ fun AddDMRelayListDialog( postViewModel.init(accountViewModel) - LaunchedEffect(postViewModel, accountViewModel.account) { + LaunchedEffect(postViewModel, accountViewModel) { postViewModel.load() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt index e23281965..6628b659a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt @@ -431,7 +431,7 @@ fun ReactionsColumn( iconSize = Size40dp, onQuotePress = { nav.nav( - Route.NewPost( + Route.NewShortNote( quote = baseNote.idHex, ), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/VideoFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/VideoFeedFilter.kt index b663e4b4e..fc20626e1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/VideoFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/dal/VideoFeedFilter.kt @@ -37,6 +37,8 @@ import com.vitorpamplona.quartz.nip68Picture.PictureEvent import com.vitorpamplona.quartz.nip68Picture.PictureMeta import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent import com.vitorpamplona.quartz.nip71Video.VideoMeta +import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent +import com.vitorpamplona.quartz.nip71Video.VideoShortEvent import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent @@ -97,6 +99,10 @@ class VideoFeedFilter( fun acceptanceEvent(noteEvent: VideoHorizontalEvent) = acceptableVideoiMetas(noteEvent.imetaTags()) + fun acceptanceEvent(noteEvent: VideoNormalEvent) = acceptableVideoiMetas(noteEvent.imetaTags()) + + fun acceptanceEvent(noteEvent: VideoShortEvent) = acceptableVideoiMetas(noteEvent.imetaTags()) + fun acceptableEvent( note: Note, params: FilterByListParams, @@ -111,6 +117,8 @@ class VideoFeedFilter( (noteEvent is FileHeaderEvent && acceptanceEvent(noteEvent)) || (noteEvent is VideoVerticalEvent && acceptanceEvent(noteEvent)) || (noteEvent is VideoHorizontalEvent && acceptanceEvent(noteEvent)) || + (noteEvent is VideoNormalEvent && acceptanceEvent(noteEvent)) || + (noteEvent is VideoShortEvent && acceptanceEvent(noteEvent)) || (noteEvent is FileStorageHeaderEvent && noteEvent.isOneOf(SUPPORTED_VIDEO_FEED_MIME_TYPES_SET)) || (noteEvent is PictureEvent && acceptanceEvent(noteEvent)) ) && diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/datasource/FeedBasis.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/datasource/FeedBasis.kt index 6915b0198..33564383d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/datasource/FeedBasis.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/datasource/FeedBasis.kt @@ -23,15 +23,31 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent import com.vitorpamplona.quartz.nip68Picture.PictureEvent import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent +import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent +import com.vitorpamplona.quartz.nip71Video.VideoShortEvent import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent val SUPPORTED_VIDEO_FEED_MIME_TYPES = listOf("image/jpeg", "image/gif", "image/png", "image/webp", "video/mp4", "video/mpeg", "video/webm", "audio/aac", "audio/mpeg", "audio/webm", "audio/wav", "image/avif") val SUPPORTED_VIDEO_FEED_MIME_TYPES_SET = SUPPORTED_VIDEO_FEED_MIME_TYPES.toSet() -val PictureAndVideoKinds = listOf(PictureEvent.KIND, VideoHorizontalEvent.KIND, VideoVerticalEvent.KIND) -val PictureAndVideoKTags = listOf(PictureEvent.KIND.toString(), VideoHorizontalEvent.KIND.toString(), VideoVerticalEvent.KIND.toString()) +val PictureAndVideoKinds = + listOf( + PictureEvent.KIND, + VideoHorizontalEvent.KIND, + VideoVerticalEvent.KIND, + VideoNormalEvent.KIND, + VideoShortEvent.KIND, + ) +val PictureAndVideoKTags = + listOf( + PictureEvent.KIND.toString(), + VideoHorizontalEvent.KIND.toString(), + VideoVerticalEvent.KIND.toString(), + VideoNormalEvent.KIND.toString(), + VideoShortEvent.KIND.toString(), + ) val PictureAndVideoLegacyKinds = listOf(FileHeaderEvent.KIND, FileStorageHeaderEvent.KIND) val PictureAndVideoLegacyKTags = listOf(FileHeaderEvent.KIND.toString(), FileStorageHeaderEvent.KIND.toString()) val LegacyMimeTypes = SUPPORTED_VIDEO_FEED_MIME_TYPES diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginScreen.kt index 8329b0b94..1eab153c2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/LoginScreen.kt @@ -112,7 +112,7 @@ fun LoginPage( val loginViewModel: LoginViewModel = viewModel() loginViewModel.init(accountStateViewModel) - LaunchedEffect(Unit) { + LaunchedEffect(loginViewModel, isFirstLogin, newAccountKey) { loginViewModel.load(isFirstLogin, newAccountKey) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt index 5cd75cd5c..e161be392 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorService.kt @@ -62,7 +62,7 @@ class TorService( delay(100) } - trySend(TorServiceStatus.Active(torService.socksPort)) + trySend(TorServiceStatus.Active(torService.socksPort, torService.torControlConnection)) Log.d("TorService", "Tor Service Connected ${torService.socksPort}") } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorServiceStatus.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorServiceStatus.kt index cb220ce2c..98269d3dc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorServiceStatus.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorServiceStatus.kt @@ -20,9 +20,12 @@ */ package com.vitorpamplona.amethyst.ui.tor +import net.freehaven.tor.control.TorControlConnection + sealed class TorServiceStatus { data class Active( val port: Int, + val torControlConnection: TorControlConnection, ) : TorServiceStatus() object Off : TorServiceStatus() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 65b0d6cb4..f44afee03 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] accompanistAdaptive = "0.37.3" activityCompose = "1.10.1" -agp = "8.12.0" +agp = "8.12.1" android-compileSdk = "36" android-minSdk = "26" android-targetSdk = "36" @@ -24,10 +24,10 @@ jacksonModuleKotlin = "2.19.2" jna = "5.17.0" jtorctl = "0.4.5.7" junit = "4.13.2" -kotlin = "2.2.0" +kotlin = "2.2.10" kotlinxCollectionsImmutable = "0.4.0" kotlinxSerialization = "1.9.0" -kotlinxSerializationPlugin = "2.2.0" +kotlinxSerializationPlugin = "2.2.10" languageId = "17.0.6" lazysodiumAndroid = "5.2.0" lifecycleRuntimeKtx = "2.9.2" @@ -43,7 +43,7 @@ rfc3986 = "0.1.2" secp256k1KmpJniAndroid = "0.18.0" securityCryptoKtx = "1.1.0" spotless = "7.2.1" -torAndroid = "0.4.8.17.1" +torAndroid = "0.4.8.17.2" translate = "17.0.3" unifiedpush = "3.0.10" urlDetector = "0.1.23" diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/EventFactory.kt b/quartz/src/main/java/com/vitorpamplona/quartz/EventFactory.kt index 68c7a8588..c98fc1119 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/EventFactory.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/EventFactory.kt @@ -108,6 +108,8 @@ import com.vitorpamplona.quartz.nip62RequestToVanish.RequestToVanishEvent import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import com.vitorpamplona.quartz.nip68Picture.PictureEvent import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent +import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent +import com.vitorpamplona.quartz.nip71Video.VideoShortEvent import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent @@ -286,6 +288,8 @@ class EventFactory { TrustedRelayListEvent.KIND -> TrustedRelayListEvent(id, pubKey, createdAt, tags, content, sig) VideoHorizontalEvent.KIND -> VideoHorizontalEvent(id, pubKey, createdAt, tags, content, sig) VideoVerticalEvent.KIND -> VideoVerticalEvent(id, pubKey, createdAt, tags, content, sig) + VideoNormalEvent.KIND -> VideoNormalEvent(id, pubKey, createdAt, tags, content, sig) + VideoShortEvent.KIND -> VideoShortEvent(id, pubKey, createdAt, tags, content, sig) VoiceEvent.KIND -> VoiceEvent(id, pubKey, createdAt, tags, content, sig) VoiceReplyEvent.KIND -> VoiceReplyEvent(id, pubKey, createdAt, tags, content, sig) WikiNoteEvent.KIND -> WikiNoteEvent(id, pubKey, createdAt, tags, content, sig) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/AudioHeaderEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/AudioHeaderEvent.kt index 617334697..dde8b8b3f 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/AudioHeaderEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/AudioHeaderEvent.kt @@ -54,7 +54,7 @@ class AudioHeaderEvent( description: String, downloadUrl: String, streamUrl: String? = null, - wavefront: List? = null, + wavefront: List? = null, createdAt: Long = TimeUtils.now(), initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate(KIND, description, createdAt) { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/TagArrayBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/TagArrayBuilderExt.kt index 5dbaaf522..d41bdf172 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/TagArrayBuilderExt.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/TagArrayBuilderExt.kt @@ -29,4 +29,4 @@ fun TagArrayBuilder.downloadUrl(downloadUrlTag: String) = addU fun TagArrayBuilder.streamUrl(streamUrl: String) = addUnique(StreamUrlTag.assemble(streamUrl)) -fun TagArrayBuilder.wavefront(wave: List) = addUnique(WaveformTag.assemble(wave)) +fun TagArrayBuilder.wavefront(wave: List) = addUnique(WaveformTag.assemble(wave)) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt index 7813f78b9..fc383cdc7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt @@ -26,7 +26,7 @@ import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper import com.vitorpamplona.quartz.utils.ensure class WaveformTag( - val wave: List, + val wave: List, ) { fun toTagArray() = assemble(wave) @@ -38,12 +38,12 @@ class WaveformTag( ensure(tag.has(1)) { return null } ensure(tag[0] == TAG_NAME) { return null } ensure(tag[1].isNotEmpty()) { return null } - val wave = runCatching { JsonMapper.mapper.readValue>(tag[1]) }.getOrNull() + val wave = runCatching { JsonMapper.mapper.readValue>(tag[1]) }.getOrNull() if (wave.isNullOrEmpty()) return null return WaveformTag(wave) } @JvmStatic - fun assemble(wave: List) = arrayOf(TAG_NAME, JsonMapper.mapper.writeValueAsString(wave)) + fun assemble(wave: List) = arrayOf(TAG_NAME, JsonMapper.mapper.writeValueAsString(wave)) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/list/EphemeralChatListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/list/EphemeralChatListEvent.kt index 9a9de6d9d..d8861a28f 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/list/EphemeralChatListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/list/EphemeralChatListEvent.kt @@ -128,7 +128,7 @@ class EphemeralChatListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), ) = resign( - content = PrivateTagsInContent.encryptNip04(privateTags, signer), + content = PrivateTagsInContent.encryptNip44(privateTags, signer), tags = tags, signer = signer, createdAt = createdAt, @@ -168,7 +168,7 @@ class EphemeralChatListEvent( initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate( kind = KIND, - description = PrivateTagsInContent.encryptNip04(privateRooms.map { it.toTagArray() }.toTypedArray(), signer), + description = PrivateTagsInContent.encryptNip44(privateRooms.map { it.toTagArray() }.toTypedArray(), signer), createdAt = createdAt, ) { alt(ALT) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt index ac17b0764..e25057691 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt @@ -133,6 +133,8 @@ class NostrClient( relayPool.disconnect() } + fun isActive() = isActive + @Synchronized fun reconnect(onlyIfChanged: Boolean = false) { if (onlyIfChanged) { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt index deb33b5db..6e7fd5c9e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt @@ -75,23 +75,27 @@ class RelayPool( fun reconnectIfNeedsToORIfItIsTime() { if (lastReconnectCall < TimeUtils.oneMinuteAgo()) { - relays.forEach { url, relay -> - if (relay.isConnected()) { - if (relay.needsToReconnect()) { - // network has changed, force reconnect - relay.disconnect() - relay.connect() - } - } else { - // relay is not connected. Connect if it is time - relay.connectAndSyncFiltersIfDisconnected() - } - } + reconnectIfNeedsTo() lastReconnectCall = TimeUtils.now() } } + fun reconnectIfNeedsTo() { + relays.forEach { url, relay -> + if (relay.isConnected()) { + if (relay.needsToReconnect()) { + // network has changed, force reconnect + relay.disconnect() + relay.connect() + } + } else { + // relay is not connected. Connect if it is time + relay.connectAndSyncFiltersIfDisconnected() + } + } + } + fun connect() = relays.forEach { url, relay -> relay.connect() diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/okhttp/BasicOkHttpWebSocket.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/okhttp/BasicOkHttpWebSocket.kt index 434abb021..f61060469 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/okhttp/BasicOkHttpWebSocket.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/okhttp/BasicOkHttpWebSocket.kt @@ -30,7 +30,7 @@ import okhttp3.Response class BasicOkHttpWebSocket( val url: NormalizedRelayUrl, - val httpClient: OkHttpClient, + val httpClient: (NormalizedRelayUrl) -> OkHttpClient, val out: WebSocketListener, ) : WebSocket { private var socket: okhttp3.WebSocket? = null @@ -74,7 +74,7 @@ class BasicOkHttpWebSocket( ) = out.onFailure(t, r?.code, r?.message) } - socket = httpClient.newWebSocket(request, listener) + socket = httpClient(url).newWebSocket(request, listener) } override fun disconnect() { @@ -85,7 +85,7 @@ class BasicOkHttpWebSocket( override fun send(msg: String): Boolean = socket?.send(msg) ?: false class Builder( - val httpClient: OkHttpClient, + val httpClient: (NormalizedRelayUrl) -> OkHttpClient, ) : WebsocketBuilder { // Called when connecting. override fun build( diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/TextNoteEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/TextNoteEvent.kt index 44d694466..5628336f5 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/TextNoteEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/TextNoteEvent.kt @@ -118,6 +118,15 @@ class TextNoteEvent( return ALT + msg.take(50) + "..." } + fun build( + note: String, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, note, createdAt) { + alt(shortedMessageForAlt(note)) + initializer() + } + fun build( note: String, replyingTo: EventHintBundle? = null, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/TagArrayBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/TagArrayBuilderExt.kt index acd905d45..e2f13176b 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/TagArrayBuilderExt.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/TagArrayBuilderExt.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.quartz.nip10Notes.tags +import android.R.attr.tag import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder import com.vitorpamplona.quartz.nip01Core.tags.people.PTag import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent @@ -27,7 +28,7 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent fun TagArrayBuilder.markedETag(tag: MarkedETag) = add(tag.toTagArray()) -fun TagArrayBuilder.markedETags(tag: List) = addAll(tag.map { it.toTagArray() }) +fun TagArrayBuilder.markedETags(tags: List) = addAll(tags.map { it.toTagArray() }) fun TagArrayBuilder.notify(tag: PTag) = add(tag.toTagArray()) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/list/ChannelListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/list/ChannelListEvent.kt index 49e72ed80..78773ba54 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/list/ChannelListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/list/ChannelListEvent.kt @@ -155,7 +155,7 @@ class ChannelListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), ) = resign( - content = PrivateTagsInContent.encryptNip04(privateTags, signer), + content = PrivateTagsInContent.encryptNip44(privateTags, signer), tags = tags, signer = signer, createdAt = createdAt, @@ -206,7 +206,7 @@ class ChannelListEvent( initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate( kind = KIND, - description = PrivateTagsInContent.encryptNip04(privateChannels.map { it.toTagArray() }.toTypedArray(), signer), + description = PrivateTagsInContent.encryptNip44(privateChannels.map { it.toTagArray() }.toTypedArray(), signer), createdAt = createdAt, ) { alt(ALT) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftEventCache.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftWrapCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftEventCache.kt rename to quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftWrapCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftWrapEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftWrapEvent.kt index bae8d9b69..7d3966bb9 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftWrapEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftWrapEvent.kt @@ -34,6 +34,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag import com.vitorpamplona.quartz.nip01Core.tags.kinds.kind import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.nip40Expiration.expiration import com.vitorpamplona.quartz.utils.TimeUtils @Immutable @@ -91,6 +92,11 @@ class DraftWrapEvent( alt(ALT_DESCRIPTION) dTag(dTag) kind(draft.kind) + expiration(TimeUtils.ninetyDaysFromNow()) + + if (draft is ExposeInDraft) { + addAll(draft.exposeInDraft()) + } initializer() } @@ -105,6 +111,7 @@ class DraftWrapEvent( createdAt = createdAt, ) { alt(ALT_DESCRIPTION) + expiration(TimeUtils.oneMinuteFromNow()) dTag(dTag) initializer() } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/PrivateTagArrayBuilder.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/PrivateTagArrayBuilder.kt index 453a7b44a..9abbee2b8 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/PrivateTagArrayBuilder.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/PrivateTagArrayBuilder.kt @@ -33,7 +33,7 @@ class PrivateTagArrayBuilder { ): Pair>> = if (toPrivate) { val encryptedTags = - PrivateTagsInContent.encryptNip04( + PrivateTagsInContent.encryptNip44( privateTags = tags, signer = signer, ) @@ -51,7 +51,7 @@ class PrivateTagArrayBuilder { if (toPrivate) { val privateTags = current.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException() val encryptedTags = - PrivateTagsInContent.encryptNip04( + PrivateTagsInContent.encryptNip44( privateTags = privateTags.plus(newTag), signer = signer, ) @@ -69,7 +69,7 @@ class PrivateTagArrayBuilder { if (toPrivate) { val privateTags = current.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException() val encryptedTags = - PrivateTagsInContent.encryptNip04( + PrivateTagsInContent.encryptNip44( privateTags = privateTags.plus(newTag), signer = signer, ) @@ -112,7 +112,7 @@ class PrivateTagArrayBuilder { ): Pair>> { val privateTags = current.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException() val encryptedTags = - PrivateTagsInContent.encryptNip04( + PrivateTagsInContent.encryptNip44( privateTags = privateTags.replaceAll(oldTagStartsWith, newTag), signer = signer, ) @@ -127,7 +127,7 @@ class PrivateTagArrayBuilder { ): Pair>> { val privateTags = current.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException() val encryptedTags = - PrivateTagsInContent.encryptNip04( + PrivateTagsInContent.encryptNip44( privateTags = privateTags.remove(oldTagStartsWith), signer = signer, ) @@ -141,7 +141,7 @@ class PrivateTagArrayBuilder { ): Pair>> { val privateTags = current.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException() val encryptedTags = - PrivateTagsInContent.encryptNip04( + PrivateTagsInContent.encryptNip44( privateTags = privateTags.remove(oldTagStartsWith), signer = signer, ) @@ -161,7 +161,7 @@ class PrivateTagArrayBuilder { ): Pair>> { val privateTags = current.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException() val encryptedTags = - PrivateTagsInContent.encryptNip04( + PrivateTagsInContent.encryptNip44( privateTags = privateTags.remove(oldTagStartsWith), signer = signer, ) @@ -174,7 +174,7 @@ class PrivateTagArrayBuilder { signer: NostrSigner, ): Pair>> { val encryptedTags = - PrivateTagsInContent.encryptNip04( + PrivateTagsInContent.encryptNip44( privateTags = arrayOf(newTag), signer = signer, ) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/bookmarkList/BookmarkListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/bookmarkList/BookmarkListEvent.kt index 2aa6f41dc..eca37af6d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/bookmarkList/BookmarkListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/bookmarkList/BookmarkListEvent.kt @@ -154,7 +154,7 @@ class BookmarkListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), ) = resign( - content = PrivateTagsInContent.encryptNip04(privateTags, signer), + content = PrivateTagsInContent.encryptNip44(privateTags, signer), tags = tags, signer = signer, createdAt = createdAt, @@ -198,7 +198,7 @@ class BookmarkListEvent( initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate( kind = KIND, - description = PrivateTagsInContent.encryptNip04(privateBookmarks.map { it.toTagArray() }.toTypedArray(), signer), + description = PrivateTagsInContent.encryptNip44(privateBookmarks.map { it.toTagArray() }.toTypedArray(), signer), createdAt = createdAt, ) { dTag(dTag) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/encryption/PrivateTagsInContent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/encryption/PrivateTagsInContent.kt index fec602548..daaab9d29 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/encryption/PrivateTagsInContent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/encryption/PrivateTagsInContent.kt @@ -48,15 +48,6 @@ class PrivateTagsInContent { } } - suspend fun encryptNip04( - privateTags: Array>? = null, - signer: NostrSigner, - ): String = - signer.nip04Encrypt( - if (privateTags.isNullOrEmpty()) "" else encode(privateTags), - signer.pubKey, - ) - suspend fun encryptNip44( privateTags: Array>? = null, signer: NostrSigner, @@ -74,15 +65,6 @@ class PrivateTagsInContent { return decode(json) } - fun encryptNip04( - privateTags: Array>? = null, - signer: NostrSignerSync, - ): String = - signer.nip04Encrypt( - if (privateTags.isNullOrEmpty()) "" else encode(privateTags), - signer.pubKey, - ) - fun encryptNip44( privateTags: Array>? = null, signer: NostrSignerSync, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/geohashList/GeohashListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/geohashList/GeohashListEvent.kt index 622e2767e..ad98fd778 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/geohashList/GeohashListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/geohashList/GeohashListEvent.kt @@ -157,7 +157,7 @@ class GeohashListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), ) = resign( - content = PrivateTagsInContent.encryptNip04(privateTags, signer), + content = PrivateTagsInContent.encryptNip44(privateTags, signer), tags = tags, signer = signer, createdAt = createdAt, @@ -208,7 +208,7 @@ class GeohashListEvent( initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate( kind = KIND, - description = PrivateTagsInContent.encryptNip04(privateGeohashes.map { GeoHashTag.assembleSingle(it) }.toTypedArray(), signer), + description = PrivateTagsInContent.encryptNip44(privateGeohashes.map { GeoHashTag.assembleSingle(it) }.toTypedArray(), signer), createdAt = createdAt, ) { alt(ALT) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/hashtagList/HashtagListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/hashtagList/HashtagListEvent.kt index 1fc707120..cfc0c74e1 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/hashtagList/HashtagListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/hashtagList/HashtagListEvent.kt @@ -154,7 +154,7 @@ class HashtagListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), ) = resign( - content = PrivateTagsInContent.encryptNip04(privateTags, signer), + content = PrivateTagsInContent.encryptNip44(privateTags, signer), tags = tags, signer = signer, createdAt = createdAt, @@ -205,7 +205,7 @@ class HashtagListEvent( initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate( kind = KIND, - description = PrivateTagsInContent.encryptNip04(privateHashtags.map { HashtagTag.assemble(it) }.toTypedArray(), signer), + description = PrivateTagsInContent.encryptNip44(privateHashtags.map { HashtagTag.assemble(it) }.toTypedArray(), signer), createdAt = createdAt, ) { alt(ALT) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/muteList/MuteListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/muteList/MuteListEvent.kt index 70055d4d9..5b860b273 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/muteList/MuteListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/muteList/MuteListEvent.kt @@ -139,7 +139,7 @@ class MuteListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), ) = resign( - content = PrivateTagsInContent.encryptNip04(privateTags, signer), + content = PrivateTagsInContent.encryptNip44(privateTags, signer), tags = publicTags, signer = signer, createdAt = createdAt, @@ -179,7 +179,7 @@ class MuteListEvent( initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate( kind = KIND, - description = PrivateTagsInContent.encryptNip04(privateMutes.map { it.toTagArray() }.toTypedArray(), signer), + description = PrivateTagsInContent.encryptNip44(privateMutes.map { it.toTagArray() }.toTypedArray(), signer), createdAt = createdAt, ) { alt(ALT) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/peopleList/PeopleListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/peopleList/PeopleListEvent.kt index 35a024698..753612ada 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/peopleList/PeopleListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/peopleList/PeopleListEvent.kt @@ -161,7 +161,7 @@ class PeopleListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), ) = resign( - content = PrivateTagsInContent.encryptNip04(privateTags, signer), + content = PrivateTagsInContent.encryptNip44(privateTags, signer), tags = publicTags, signer = signer, createdAt = createdAt, @@ -205,7 +205,7 @@ class PeopleListEvent( initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate( kind = KIND, - description = PrivateTagsInContent.encryptNip04(privatePeople.map { it.toTagArray() }.toTypedArray(), signer), + description = PrivateTagsInContent.encryptNip44(privatePeople.map { it.toTagArray() }.toTypedArray(), signer), createdAt = createdAt, ) { dTag(dTag) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/relayLists/BlockedRelayListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/relayLists/BlockedRelayListEvent.kt index 3798037bd..10f12efd5 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/relayLists/BlockedRelayListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip51Lists/relayLists/BlockedRelayListEvent.kt @@ -108,7 +108,7 @@ class BlockedRelayListEvent( initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate( kind = KIND, - description = PrivateTagsInContent.encryptNip04(privateRelays.map { RelayTag.assemble(it) }.toTypedArray(), signer), + description = PrivateTagsInContent.encryptNip44(privateRelays.map { RelayTag.assemble(it) }.toTypedArray(), signer), createdAt = createdAt, ) { alt(ALT) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/VideoNormalEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/VideoNormalEvent.kt new file mode 100644 index 000000000..bdb3fabb6 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/VideoNormalEvent.kt @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip71Video + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag +import com.vitorpamplona.quartz.nip22Comments.RootScope +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.utils.TimeUtils +import java.util.UUID + +@Immutable +class VideoNormalEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), + RootScope { + companion object { + const val KIND = 21 + const val ALT_DESCRIPTION = "Horizontal Video" + + fun build( + video: VideoMeta, + description: String, + dTag: String = UUID.randomUUID().toString(), + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = build(description, dTag, createdAt) { + videoIMeta(video) + initializer() + } + + fun build( + video: List, + description: String, + dTag: String = UUID.randomUUID().toString(), + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = build(description, dTag, createdAt) { + videoIMetas(video) + initializer() + } + + fun build( + description: String, + dTag: String = UUID.randomUUID().toString(), + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, description, createdAt) { + dTag(dTag) + alt(ALT_DESCRIPTION) + initializer() + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftBuilder.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/VideoShortEvent.kt similarity index 52% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftBuilder.kt rename to quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/VideoShortEvent.kt index 3435d2227..4be29bde8 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftBuilder.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip71Video/VideoShortEvent.kt @@ -18,37 +18,52 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.quartz.nip37Drafts +package com.vitorpamplona.quartz.nip71Video -import com.vitorpamplona.quartz.nip01Core.core.Event -import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag -import com.vitorpamplona.quartz.nip01Core.tags.kinds.kind +import com.vitorpamplona.quartz.nip22Comments.RootScope import com.vitorpamplona.quartz.nip31Alts.alt import com.vitorpamplona.quartz.utils.TimeUtils +import java.util.UUID -class DraftBuilder { +@Immutable +class VideoShortEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig), + RootScope { companion object { - suspend fun encryptAndSign( - dTag: String, - draft: T, - signer: NostrSigner, + const val KIND = 22 + const val ALT_DESCRIPTION = "Vertical Video" + + fun build( + video: VideoMeta, + description: String, + dTag: String = UUID.randomUUID().toString(), createdAt: Long = TimeUtils.now(), - ): DraftWrapEvent { - val encryptedContent = signer.nip44Encrypt(draft.toJson(), signer.pubKey) - val template = - eventTemplate(DraftWrapEvent.KIND, encryptedContent, createdAt) { - alt(DraftWrapEvent.ALT_DESCRIPTION) - dTag(dTag) - kind(draft.kind) + initializer: TagArrayBuilder.() -> Unit = {}, + ) = build(description, dTag, createdAt) { + videoIMeta(video) + initializer() + } - if (draft is ExposeInDraft) { - addAll(draft.exposeInDraft()) - } - } - - return signer.sign(template) + fun build( + description: String, + dTag: String = UUID.randomUUID().toString(), + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, description, createdAt) { + dTag(dTag) + alt(ALT_DESCRIPTION) + initializer() } } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/CommunityListEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/CommunityListEvent.kt index aaaaab456..0bd9bbc59 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/CommunityListEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/CommunityListEvent.kt @@ -178,7 +178,7 @@ class CommunityListEvent( signer: NostrSigner, createdAt: Long = TimeUtils.now(), ) = resign( - content = PrivateTagsInContent.encryptNip04(privateTags, signer), + content = PrivateTagsInContent.encryptNip44(privateTags, signer), tags = tags, signer = signer, createdAt = createdAt, @@ -218,7 +218,7 @@ class CommunityListEvent( initializer: TagArrayBuilder.() -> Unit = {}, ) = eventTemplate( kind = KIND, - description = PrivateTagsInContent.encryptNip04(privateCommunities.map { it.toTagArray() }.toTypedArray(), signer), + description = PrivateTagsInContent.encryptNip44(privateCommunities.map { it.toTagArray() }.toTypedArray(), signer), createdAt = createdAt, ) { alt(ALT) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/AudioMeta.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/AudioMeta.kt index 8d035e720..d74160ccc 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/AudioMeta.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/AudioMeta.kt @@ -29,7 +29,7 @@ data class AudioMeta( val mimeType: String? = null, val hash: String? = null, val duration: Int? = null, - val waveform: List? = null, + val waveform: List? = null, ) { fun toIMetaArray(): Array = IMetaTagBuilder(url) @@ -37,7 +37,7 @@ data class AudioMeta( mimeType?.let { mimeType(it) } hash?.let { hash(it) } duration?.let { duration(it) } - waveform?.let { waveform(it) } + waveform?.let { waveformFloat(it) } }.build() .toTagArray() diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagBuilderExt.kt index 4562d5275..25da88f79 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagBuilderExt.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagBuilderExt.kt @@ -34,6 +34,8 @@ fun IMetaTagBuilder.hash(hash: HexKey) = add(HashSha256Tag.TAG_NAME, hash) fun IMetaTagBuilder.duration(size: Int) = add(DurationTag.TAG_NAME, size.toString()) -fun IMetaTagBuilder.waveform(wave: List) = add(WaveformTag.TAG_NAME, WaveformTag.assembleWave(wave)) +fun IMetaTagBuilder.waveformInt(wave: List) = add(WaveformTag.TAG_NAME, WaveformTag.assembleWaveInt(wave)) + +fun IMetaTagBuilder.waveformFloat(wave: List) = add(WaveformTag.TAG_NAME, WaveformTag.assembleWaveFloat(wave)) fun IMetaTagBuilder.mimeType(mime: String) = add(MimeTypeTag.TAG_NAME, mime) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/TagArrayBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/TagArrayBuilderExt.kt index f482eb201..42352f12d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/TagArrayBuilderExt.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/TagArrayBuilderExt.kt @@ -33,7 +33,7 @@ fun TagArrayBuilder.audioIMeta( mimeType: String? = null, hash: String? = null, duration: Int? = null, - waveform: List? = null, + waveform: List? = null, ) = audioIMeta(AudioMeta(url, mimeType, hash, duration, waveform)) fun TagArrayBuilder.audioIMeta(imeta: AudioMeta): TagArrayBuilder { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceEvent.kt index 33f30d596..60d94c44c 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceEvent.kt @@ -43,7 +43,7 @@ class VoiceEvent( mimeType: String?, hash: String, duration: Int, - waveform: List, + waveform: List, ) = build(AudioMeta(url, mimeType, hash, duration, waveform)) fun build( diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceReplyEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceReplyEvent.kt index eb7444881..2b064deb3 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceReplyEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceReplyEvent.kt @@ -64,7 +64,7 @@ class VoiceReplyEvent( mimeType: String?, hash: String, duration: Int, - waveform: List, + waveform: List, replyingTo: EventHintBundle, ) = build(AudioMeta(url, mimeType, hash, duration, waveform), replyingTo) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/WaveformTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/WaveformTag.kt index d4366fcba..a05d9a5a2 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/WaveformTag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/WaveformTag.kt @@ -22,11 +22,12 @@ package com.vitorpamplona.quartz.nipA0VoiceMessages.tags import com.vitorpamplona.quartz.nip01Core.core.has import com.vitorpamplona.quartz.utils.ensure +import kotlin.collections.joinToString class WaveformTag( - val wave: List, + val wave: List, ) { - fun toTagArray() = assemble(wave) + fun toTagArray() = assembleFloat(wave) companion object { const val TAG_NAME = "waveform" @@ -35,26 +36,32 @@ class WaveformTag( fun parse(tag: Array): WaveformTag? = parseWave(tag)?.let { WaveformTag(it) } @JvmStatic - fun parseWave(tag: Array): List? { + fun parseWave(tag: Array): List? { ensure(tag.has(1)) { return null } ensure(tag[0] == TAG_NAME) { return null } ensure(tag[1].isNotEmpty()) { return null } - val wave = tag[1].split(" ").mapNotNull { it.toIntOrNull() } + val wave = tag[1].split(" ").mapNotNull { it.toFloatOrNull() } if (wave.isEmpty()) return null return wave } - fun parseWave(wave: String): List? { - val wave = wave.split(" ").mapNotNull { it.toIntOrNull() } + fun parseWave(wave: String): List? { + val wave = wave.split(" ").mapNotNull { it.toFloatOrNull() } if (wave.isEmpty()) return null return wave } @JvmStatic - fun assembleWave(wave: List) = wave.joinToString(" ") + fun assembleWaveInt(wave: List) = wave.joinToString(" ") @JvmStatic - fun assemble(wave: List) = arrayOf(TAG_NAME, assembleWave(wave)) + fun assembleInt(wave: List) = arrayOf(TAG_NAME, assembleWaveInt(wave)) + + @JvmStatic + fun assembleWaveFloat(wave: List) = wave.joinToString(" ") + + @JvmStatic + fun assembleFloat(wave: List) = arrayOf(TAG_NAME, assembleWaveFloat(wave)) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeSoftCache.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeSoftCache.kt index 995c0701c..99127c13c 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeSoftCache.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeSoftCache.kt @@ -31,18 +31,24 @@ class LargeSoftCache : CacheOperations { fun get(key: K): V? { val softRef = cache.get(key) - val value = softRef?.get() + if (softRef == null) return null + val value = softRef.get() return if (value != null) { value } else { - cache.remove(key) + cache.remove(key, softRef) null } } fun remove(key: K) = cache.remove(key) + fun removeIf( + key: K, + value: WeakReference, + ) = cache.remove(key, value) + override fun size() = cache.size fun isEmpty() = cache.isEmpty() @@ -79,13 +85,21 @@ class LargeSoftCache : CacheOperations { builder: (key: K) -> V, ): V { val softRef = cache[key] - val value = softRef?.get() - return if (value != null) { - value - } else { + return if (softRef == null) { val newObject = builder(key) cache.putIfAbsent(key, WeakReference(newObject))?.get() ?: newObject + } else { + val value = softRef.get() + if (value != null) { + value + } else { + // removes first to make sure the putIfAbsent works. + // another thread may put in between + cache.remove(key, softRef) + val newObject = builder(key) + return cache.putIfAbsent(key, WeakReference(newObject))?.get() ?: newObject + } } } @@ -95,14 +109,14 @@ class LargeSoftCache : CacheOperations { * this method can be called periodically or when memory pressure is high. */ fun cleanUp() { - val keysToRemove = mutableListOf() - forEach { key, softRef -> - if (softRef == null) { - keysToRemove.add(key) + val keysToRemove = mutableMapOf>() + cache.forEach { key, softRef -> + if (softRef.get() == null) { + keysToRemove.put(key, softRef) } } - keysToRemove.forEach { key -> - cache.remove(key) + keysToRemove.forEach { key, value -> + cache.remove(key, value) println("Cleaned up entry for key: $key (object was garbage collected)") } println("Cache cleanup completed. Remaining size: ${cache.size}") @@ -122,7 +136,7 @@ class LargeSoftCache : CacheOperations { ) { val value = ref.get() if (value == null) { - cache.remove(k) + cache.removeIf(k, ref) } else { inner.accept(k, value) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt index 6f46cb0d9..d271d7b8a 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt @@ -28,6 +28,7 @@ object TimeUtils { const val ONE_HOUR = 60 * ONE_MINUTE const val EIGHT_HOURS = 8 * ONE_HOUR const val ONE_DAY = 24 * ONE_HOUR + const val NINETY_DAYS = 90 * ONE_DAY const val ONE_WEEK = 7 * ONE_DAY const val ONE_MONTH = 30 * ONE_DAY const val ONE_YEAR = 365 * ONE_DAY @@ -61,4 +62,6 @@ object TimeUtils { fun oneMonthAgo() = now() - ONE_MONTH fun randomWithTwoDays() = now() - RandomInstance.int(twoDays()) + + fun ninetyDaysFromNow() = now() + NINETY_DAYS }