Fix merge-related conflicts.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.encryption.AESGCM
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import okhttp3.Request
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class DMDecryptionTest {
|
||||
val okHttp = HttpClientManager.getHttpClient(false)
|
||||
|
||||
val url = "https://cdn.satellite.earth/812fd4cf9d4d4b59c141ecd6a6c08c7571b5872237ad6477916cb2d119b5cacd"
|
||||
val cipher =
|
||||
AESGCM(
|
||||
"7b184b3849e161027bab1aac9f2d96eb22bfe02c8dc34cad5d2cc436a64f191d".hexToByteArray(),
|
||||
"3936923383652e3f0d72bfd40568b435".hexToByteArray(),
|
||||
)
|
||||
val decryptedSize = 1277122
|
||||
val expectedMimeType = "video/mp4"
|
||||
|
||||
@Test
|
||||
fun runDownloadAndDecryptVideo() {
|
||||
HttpClientManager.addCipherToCache(url, cipher, "video/mp4")
|
||||
|
||||
val request =
|
||||
Request
|
||||
.Builder()
|
||||
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
||||
.url(url)
|
||||
.get()
|
||||
.build()
|
||||
|
||||
okHttp.newCall(request).execute().use {
|
||||
assertEquals(decryptedSize, it.body.bytes().size)
|
||||
assertEquals(expectedMimeType, it.body.contentType().toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,9 +34,9 @@ import com.vitorpamplona.amethyst.service.uploads.nip96.ServerInfoRetriever
|
||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
|
||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
|
||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
||||
import com.vitorpamplona.quartz.CryptoUtils
|
||||
import com.vitorpamplona.quartz.nip01Core.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.utils.sha256.sha256
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.fail
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -81,7 +81,7 @@ class ImageUploadTesting {
|
||||
|
||||
private suspend fun testBlossom(server: ServerName) {
|
||||
val paylod = getBitmap()
|
||||
val initialHash = CryptoUtils.sha256(paylod).toHexKey()
|
||||
val initialHash = sha256(paylod).toHexKey()
|
||||
val inputStream = paylod.inputStream()
|
||||
val result =
|
||||
BlossomUploader()
|
||||
@@ -111,7 +111,7 @@ class ImageUploadTesting {
|
||||
return
|
||||
}
|
||||
|
||||
val downloadedHash = CryptoUtils.sha256(imageData).toHexKey()
|
||||
val downloadedHash = sha256(imageData).toHexKey()
|
||||
assertEquals(initialHash, downloadedHash)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,11 @@ package com.vitorpamplona.amethyst
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.amethyst.service.ots.OkHttpBlockstreamExplorer
|
||||
import com.vitorpamplona.amethyst.service.ots.OkHttpCalendarBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.OpenTimestamps
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Assert
|
||||
@@ -46,27 +47,27 @@ class OkHttpOtsTest {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
OtsEvent.otsInstance = OpenTimestamps(OkHttpBlockstreamExplorer(forceProxy = { false }), OkHttpCalendarBuilder(forceProxy = { false }))
|
||||
OtsResolver.ots = OpenTimestamps(OkHttpBlockstreamExplorer(forceProxy = { false }), OkHttpCalendarBuilder(forceProxy = { false }))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyNostrEvent() {
|
||||
val ots = EventMapper.fromJson(otsEvent) as OtsEvent
|
||||
println(ots.info())
|
||||
println(OtsResolver.info(ots.otsByteArray()))
|
||||
assertEquals(1707688818L, ots.verify())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyNostrEvent2() {
|
||||
val ots = EventMapper.fromJson(otsEvent2) as OtsEvent
|
||||
println(ots.info())
|
||||
println(OtsResolver.info(ots.otsByteArray()))
|
||||
assertEquals(1706322179L, ots.verify())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun verifyNostrPendingEvent() {
|
||||
val ots = EventMapper.fromJson(otsPendingEvent) as OtsEvent
|
||||
println(ots.info())
|
||||
println(OtsResolver.info(ots.otsByteArray()))
|
||||
assertEquals(null, ots.verify())
|
||||
}
|
||||
|
||||
@@ -79,7 +80,7 @@ class OkHttpOtsTest {
|
||||
|
||||
val otsFile = OtsEvent.stamp(otsEvent2Digest)
|
||||
|
||||
OtsEvent.create(otsEvent2Digest, otsFile, signer) {
|
||||
signer.sign(OtsEvent.build(otsEvent2Digest, otsFile)) {
|
||||
ots = it
|
||||
countDownLatch.countDown()
|
||||
}
|
||||
@@ -87,7 +88,7 @@ class OkHttpOtsTest {
|
||||
Assert.assertTrue(countDownLatch.await(1, TimeUnit.SECONDS))
|
||||
|
||||
println(ots!!.toJson())
|
||||
println(ots!!.info())
|
||||
println(OtsResolver.info(ots!!.otsByteArray()))
|
||||
|
||||
// Should not be valid because we need to wait for confirmations
|
||||
assertEquals(null, ots!!.verify())
|
||||
|
||||
+4
-4
@@ -26,11 +26,11 @@ import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.hasValidSignature
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.verify
|
||||
import junit.framework.TestCase
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -42,7 +42,7 @@ import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ThreadAssemblerTest {
|
||||
class ThreadDualAxisChartAssemblerTest {
|
||||
val db =
|
||||
"""
|
||||
[
|
||||
@@ -125,7 +125,7 @@ class ThreadAssemblerTest {
|
||||
|
||||
var counter = 0
|
||||
eventArray.forEach {
|
||||
TestCase.assertTrue("${it.id} failed signature check", it.hasValidSignature())
|
||||
TestCase.assertTrue("${it.id} failed signature check", it.verify())
|
||||
LocalCache.verifyAndConsume(it, null)
|
||||
counter++
|
||||
}
|
||||
+2
-2
@@ -27,8 +27,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.ui.actions.buildAnnotatedStringWithUrlHighlighting
|
||||
import com.vitorpamplona.quartz.nip01Core.UserMetadata
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
||||
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKey
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateDMChannel
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.getOrCreateZapChannel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -45,15 +45,15 @@ import com.vitorpamplona.amethyst.ui.tor.TorSettingsFlow
|
||||
import com.vitorpamplona.amethyst.ui.tor.TorType
|
||||
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
|
||||
import com.vitorpamplona.quartz.experimental.edits.PrivateOutboxRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
|
||||
|
||||
@@ -57,8 +57,8 @@ import com.vitorpamplona.amethyst.service.ots.OkHttpCalendarBuilder
|
||||
import com.vitorpamplona.amethyst.ui.tor.TorManager
|
||||
import com.vitorpamplona.amethyst.ui.tor.TorType
|
||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.OpenTimestamps
|
||||
import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
|
||||
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull
|
||||
@@ -110,13 +110,13 @@ class ServiceManager(
|
||||
else -> HttpClientManager.setDefaultProxy(null)
|
||||
}
|
||||
|
||||
OtsEvent.otsInstance =
|
||||
OtsResolver.ots =
|
||||
OpenTimestamps(
|
||||
OkHttpBlockstreamExplorer(myAccount::shouldUseTorForMoneyOperations),
|
||||
OkHttpCalendarBuilder(myAccount::shouldUseTorForMoneyOperations),
|
||||
)
|
||||
} else {
|
||||
OtsEvent.otsInstance =
|
||||
OtsResolver.ots =
|
||||
OpenTimestamps(
|
||||
OkHttpBlockstreamExplorer { false },
|
||||
OkHttpCalendarBuilder { false },
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -30,13 +30,13 @@ import com.vitorpamplona.amethyst.ui.tor.TorSettingsFlow
|
||||
import com.vitorpamplona.ammolite.relays.Constants
|
||||
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
|
||||
import com.vitorpamplona.quartz.experimental.edits.PrivateOutboxRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
|
||||
|
||||
@@ -26,8 +26,8 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.ui.note.njumpLink
|
||||
import com.vitorpamplona.ammolite.relays.Relay
|
||||
import com.vitorpamplona.ammolite.relays.RelayStats
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
@@ -28,12 +28,16 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.ammolite.relays.Relay
|
||||
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNAddr
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.base.ChannelData
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -41,11 +45,23 @@ import kotlinx.coroutines.Dispatchers
|
||||
class PublicChatChannel(
|
||||
idHex: String,
|
||||
) : Channel(idHex) {
|
||||
var info = ChannelCreateEvent.ChannelData(null, null, null)
|
||||
var event: ChannelCreateEvent? = null
|
||||
var info = ChannelData(null, null, null, null)
|
||||
|
||||
override fun relays() = info.relays ?: super.relays()
|
||||
|
||||
fun updateChannelInfo(
|
||||
creator: User,
|
||||
channelInfo: ChannelCreateEvent.ChannelData,
|
||||
channelInfo: ChannelCreateEvent,
|
||||
updatedAt: Long,
|
||||
) {
|
||||
this.event = channelInfo
|
||||
updateChannelInfo(creator, channelInfo.channelInfo(), updatedAt)
|
||||
}
|
||||
|
||||
fun updateChannelInfo(
|
||||
creator: User,
|
||||
channelInfo: ChannelData,
|
||||
updatedAt: Long,
|
||||
) {
|
||||
this.info = channelInfo
|
||||
@@ -66,16 +82,20 @@ class PublicChatChannel(
|
||||
|
||||
@Stable
|
||||
class LiveActivitiesChannel(
|
||||
val address: ATag,
|
||||
) : Channel(address.toTag()) {
|
||||
val address: Address,
|
||||
) : Channel(address.toValue()) {
|
||||
var info: LiveActivitiesEvent? = null
|
||||
|
||||
override fun idNote() = address.toNAddr()
|
||||
override fun idNote() = toNAddr()
|
||||
|
||||
override fun idDisplayNote() = idNote().toShortenHex()
|
||||
|
||||
fun address() = address
|
||||
|
||||
override fun relays() = info?.allRelayUrls() ?: super.relays()
|
||||
|
||||
fun relayHintUrl() = relays().firstOrNull()
|
||||
|
||||
fun updateChannelInfo(
|
||||
creator: User,
|
||||
channelInfo: LiveActivitiesEvent,
|
||||
@@ -95,8 +115,16 @@ class LiveActivitiesChannel(
|
||||
listOfNotNull(info?.title(), info?.summary())
|
||||
.filter { it.contains(prefix, true) }
|
||||
.isNotEmpty()
|
||||
|
||||
fun toNAddr() = NAddress.create(address.kind, address.pubKeyHex, address.dTag, relayHintUrl())
|
||||
|
||||
fun toATag() = ATag(address, relayHintUrl())
|
||||
}
|
||||
|
||||
data class Counter(
|
||||
var number: Int = 0,
|
||||
)
|
||||
|
||||
@Stable
|
||||
abstract class Channel(
|
||||
val idHex: String,
|
||||
@@ -105,6 +133,7 @@ abstract class Channel(
|
||||
var updatedMetadataAt: Long = 0
|
||||
val notes = LargeCache<HexKey, Note>()
|
||||
var lastNoteCreatedAt: Long = 0
|
||||
private var relays = mapOf<RelayBriefInfoCache.RelayBriefInfo, Counter>()
|
||||
|
||||
open fun id() = Hex.decode(idHex)
|
||||
|
||||
@@ -120,6 +149,14 @@ abstract class Channel(
|
||||
|
||||
open fun profilePicture(): String? = creator?.info?.banner
|
||||
|
||||
open fun relays() =
|
||||
relays.keys
|
||||
.toSortedSet { o1, o2 ->
|
||||
val o1Count = relays[o1]?.number ?: 0
|
||||
val o2Count = relays[o2]?.number ?: 0
|
||||
o2Count.compareTo(o1Count) // descending
|
||||
}.map { it.url }
|
||||
|
||||
open fun updateChannelInfo(
|
||||
creator: User,
|
||||
updatedAt: Long,
|
||||
@@ -130,12 +167,35 @@ abstract class Channel(
|
||||
live.invalidateData()
|
||||
}
|
||||
|
||||
fun addNote(note: Note) {
|
||||
@Synchronized
|
||||
fun addRelaySync(briefInfo: RelayBriefInfoCache.RelayBriefInfo) {
|
||||
if (briefInfo !in relays) {
|
||||
relays = relays + Pair(briefInfo, Counter(1))
|
||||
}
|
||||
}
|
||||
|
||||
fun addRelay(relay: Relay) {
|
||||
val counter = relays[relay.brief]
|
||||
if (counter != null) {
|
||||
counter.number++
|
||||
} else {
|
||||
addRelaySync(relay.brief)
|
||||
}
|
||||
}
|
||||
|
||||
fun addNote(
|
||||
note: Note,
|
||||
relay: Relay? = null,
|
||||
) {
|
||||
notes.put(note.idHex, note)
|
||||
|
||||
if ((note.createdAt() ?: 0) > lastNoteCreatedAt) {
|
||||
lastNoteCreatedAt = note.createdAt() ?: 0
|
||||
}
|
||||
|
||||
if (relay != null) {
|
||||
addRelay(relay)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeNote(note: Note) {
|
||||
|
||||
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.model
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip14Subject.subject
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
|
||||
@@ -31,49 +31,52 @@ import com.vitorpamplona.amethyst.model.observables.LatestByKindWithETag
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.ammolite.relays.BundledInsert
|
||||
import com.vitorpamplona.ammolite.relays.Relay
|
||||
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
|
||||
import com.vitorpamplona.quartz.blossom.BlossomServersEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.edits.PrivateOutboxRelayListEvent
|
||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryReadingStateEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.FileStorageEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
|
||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||
import com.vitorpamplona.quartz.experimental.relationshipStatus.RelationshipStatusEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.checkSignature
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.tagValueContains
|
||||
import com.vitorpamplona.quartz.nip01Core.hasValidSignature
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.mapTaggedAddress
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.taggedAddresses
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.GenericETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.forEachTaggedEventId
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.isTaggedEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.mapTaggedEventId
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEvents
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUsers
|
||||
import com.vitorpamplona.quartz.nip01Core.verify
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.VerificationState
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEncryptedFileHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.decodeEventIdAsHexOrNull
|
||||
@@ -82,18 +85,18 @@ import com.vitorpamplona.quartz.nip19Bech32.isATag
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelHideMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelListEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMuteUserEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackSelectionEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitIssueEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitPatchEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitReplyEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitRepositoryEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelHideMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMuteUserEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.pack.EmojiPackEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.selection.EmojiPackSelectionEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.reply.GitReplyEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.repository.GitRepositoryEvent
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
|
||||
@@ -113,8 +116,8 @@ import com.vitorpamplona.quartz.nip52Calendar.CalendarDateSlotEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarRSVPEvent
|
||||
import com.vitorpamplona.quartz.nip52Calendar.CalendarTimeSlotEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
@@ -122,27 +125,28 @@ import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeAwardEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeProfilesEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter
|
||||
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityListEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.AppRecommendationEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.recommendation.AppRecommendationEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryRequestEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90UserDiscoveryRequestEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90UserDiscoveryResponseEvent
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.FileServersEvent
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
@@ -272,6 +276,8 @@ object LocalCache {
|
||||
|
||||
fun getAddressableNoteIfExists(key: String): AddressableNote? = addressables.get(key)
|
||||
|
||||
fun getAddressableNoteIfExists(address: Address): AddressableNote? = getAddressableNoteIfExists(address.toValue())
|
||||
|
||||
fun getNoteIfExists(key: String): Note? = addressables.get(key) ?: notes.get(key)
|
||||
|
||||
fun getNoteIfExists(key: ETag): Note? = notes.get(key.eventId)
|
||||
@@ -312,7 +318,7 @@ object LocalCache {
|
||||
val noteEvent = note.event
|
||||
return if (noteEvent is AddressableEvent) {
|
||||
// upgrade to the latest
|
||||
val newNote = checkGetOrCreateAddressableNote(noteEvent.address().toTag())
|
||||
val newNote = checkGetOrCreateAddressableNote(noteEvent.aTag().toTag())
|
||||
|
||||
if (newNote != null && newNote.event == null) {
|
||||
val author = note.author ?: getOrCreateUser(noteEvent.pubKey)
|
||||
@@ -366,9 +372,10 @@ object LocalCache {
|
||||
if (isValidHex(key)) {
|
||||
return channels.getOrCreate(key) { PublicChatChannel(key) }
|
||||
}
|
||||
val aTag = ATag.parse(key, null)
|
||||
if (aTag != null) {
|
||||
return channels.getOrCreate(aTag.toTag()) { LiveActivitiesChannel(aTag) }
|
||||
|
||||
val address = Address.parse(key)
|
||||
if (address != null) {
|
||||
return channels.getOrCreate(address.toValue()) { LiveActivitiesChannel(address) }
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -382,7 +389,7 @@ object LocalCache {
|
||||
|
||||
fun checkGetOrCreateAddressableNote(key: String): AddressableNote? =
|
||||
try {
|
||||
val addr = ATag.parse(key, null) // relay doesn't matter for the index.
|
||||
val addr = Address.parse(key)
|
||||
if (addr != null) {
|
||||
getOrCreateAddressableNote(addr)
|
||||
} else {
|
||||
@@ -393,17 +400,12 @@ object LocalCache {
|
||||
null
|
||||
}
|
||||
|
||||
fun getOrCreateAddressableNoteInternal(key: ATag): AddressableNote {
|
||||
// checkNotInMainThread()
|
||||
|
||||
// we can't use naddr here because naddr might include relay info and
|
||||
// the preferred relay should not be part of the index.
|
||||
return addressables.getOrCreate(key.toTag()) {
|
||||
fun getOrCreateAddressableNoteInternal(key: Address): AddressableNote =
|
||||
addressables.getOrCreate(key.toValue()) {
|
||||
AddressableNote(key)
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrCreateAddressableNote(key: ATag): AddressableNote {
|
||||
fun getOrCreateAddressableNote(key: Address): AddressableNote {
|
||||
val note = getOrCreateAddressableNoteInternal(key)
|
||||
// Loads the user outside a Syncronized block to avoid blocking
|
||||
if (note.author == null) {
|
||||
@@ -412,13 +414,18 @@ object LocalCache {
|
||||
return note
|
||||
}
|
||||
|
||||
fun getOrCreateNote(key: ETag): Note {
|
||||
fun getOrCreateNote(key: GenericETag): Note {
|
||||
val note = getOrCreateNote(key.eventId)
|
||||
// Loads the user outside a Syncronized block to avoid blocking
|
||||
val possibleAuthor = key.authorPubKeyHex
|
||||
val possibleAuthor = key.author
|
||||
if (note.author == null && possibleAuthor != null) {
|
||||
note.author = checkGetOrCreateUser(possibleAuthor)
|
||||
}
|
||||
val relayHint = key.relay
|
||||
if (!relayHint.isNullOrBlank()) {
|
||||
val relay = RelayBriefInfoCache.get(RelayUrlFormatter.normalize(relayHint))
|
||||
note.addRelayBrief(relay)
|
||||
}
|
||||
return note
|
||||
}
|
||||
|
||||
@@ -521,7 +528,7 @@ object LocalCache {
|
||||
|
||||
val replyTo = computeReplyTo(event)
|
||||
|
||||
if (event is BaseTextNoteEvent && antiSpam.isSpam(event, relay)) {
|
||||
if (event is BaseThreadedEvent && antiSpam.isSpam(event, relay)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -676,12 +683,14 @@ object LocalCache {
|
||||
is BadgeAwardEvent -> event.awardDefinition().map { getOrCreateAddressableNote(it) }
|
||||
is PrivateDmEvent -> event.taggedEvents().mapNotNull { checkGetOrCreateNote(it) }
|
||||
is RepostEvent ->
|
||||
event.boostedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
||||
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
|
||||
event.boostedEventIds().mapNotNull { checkGetOrCreateNote(it) } +
|
||||
event.boostedAddresses().map { getOrCreateAddressableNote(it) }
|
||||
is GenericRepostEvent ->
|
||||
event.boostedPost().mapNotNull { checkGetOrCreateNote(it) } +
|
||||
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
|
||||
is CommunityPostApprovalEvent -> event.approvedEvents().mapNotNull { checkGetOrCreateNote(it) }
|
||||
event.boostedEventIds().mapNotNull { checkGetOrCreateNote(it) } +
|
||||
event.boostedAddresses().map { getOrCreateAddressableNote(it) }
|
||||
is CommunityPostApprovalEvent ->
|
||||
event.approvedEvents().mapNotNull { checkGetOrCreateNote(it) } +
|
||||
event.approvedAddresses().map { getOrCreateAddressableNote(it) }
|
||||
is ReactionEvent ->
|
||||
event.originalPost().mapNotNull { checkGetOrCreateNote(it) } +
|
||||
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
|
||||
@@ -691,7 +700,7 @@ object LocalCache {
|
||||
is ChannelMessageEvent ->
|
||||
event
|
||||
.tagsWithoutCitations()
|
||||
.filter { it != event.channel() }
|
||||
.filter { it != event.channelId() }
|
||||
.mapNotNull { checkGetOrCreateNote(it) }
|
||||
is LiveActivitiesChatMessageEvent ->
|
||||
event
|
||||
@@ -731,11 +740,13 @@ object LocalCache {
|
||||
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||
note.loadEvent(event, author, emptyList())
|
||||
|
||||
val channel =
|
||||
getOrCreateChannel(note.idHex) { LiveActivitiesChannel(note.address) }
|
||||
as? LiveActivitiesChannel
|
||||
val channel = getOrCreateChannel(note.idHex) { LiveActivitiesChannel(note.address) } as? LiveActivitiesChannel
|
||||
|
||||
val creator = event.host()?.ifBlank { null }?.let { checkGetOrCreateUser(it) } ?: author
|
||||
if (relay != null) {
|
||||
channel?.addRelay(relay)
|
||||
}
|
||||
|
||||
val creator = event.host()?.let { checkGetOrCreateUser(it.pubKey) } ?: author
|
||||
|
||||
channel?.updateChannelInfo(creator, event, event.createdAt)
|
||||
|
||||
@@ -1119,7 +1130,7 @@ object LocalCache {
|
||||
}
|
||||
}
|
||||
|
||||
val addressList = event.deleteAddressTags()
|
||||
val addressList = event.deleteAddressIds()
|
||||
val addressSet = addressList.toSet()
|
||||
|
||||
addressList
|
||||
@@ -1182,7 +1193,7 @@ object LocalCache {
|
||||
getChannelIfExists(it.toTag())?.removeNote(deleteNote)
|
||||
}
|
||||
|
||||
(deletedEvent as? TorrentCommentEvent)?.torrent()?.let {
|
||||
(deletedEvent as? TorrentCommentEvent)?.torrentIds()?.let {
|
||||
getNoteIfExists(it)?.removeReply(deleteNote)
|
||||
}
|
||||
|
||||
@@ -1284,7 +1295,7 @@ object LocalCache {
|
||||
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
val communities = event.communities()
|
||||
val communities = event.communityAddresses()
|
||||
val eventsApproved = computeReplyTo(event)
|
||||
|
||||
val repliesTo = communities.map { getOrCreateAddressableNote(it) }
|
||||
@@ -1353,14 +1364,17 @@ object LocalCache {
|
||||
refreshObservers(note)
|
||||
}
|
||||
|
||||
fun consume(event: ChannelCreateEvent) {
|
||||
fun consume(
|
||||
event: ChannelCreateEvent,
|
||||
relay: Relay?,
|
||||
) {
|
||||
// Log.d("MT", "New Event ${event.content} ${event.id.toHex()}")
|
||||
val oldChannel = getOrCreateChannel(event.id) { PublicChatChannel(it) }
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
val note = getOrCreateNote(event.id)
|
||||
if (note.event == null) {
|
||||
oldChannel.addNote(note)
|
||||
oldChannel.addNote(note, relay)
|
||||
note.loadEvent(event, author, emptyList())
|
||||
|
||||
refreshObservers(note)
|
||||
@@ -1371,13 +1385,16 @@ object LocalCache {
|
||||
}
|
||||
if (oldChannel.creator == null || oldChannel.creator == author) {
|
||||
if (oldChannel is PublicChatChannel) {
|
||||
oldChannel.updateChannelInfo(author, event.channelInfo(), event.createdAt)
|
||||
oldChannel.updateChannelInfo(author, event, event.createdAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun consume(event: ChannelMetadataEvent) {
|
||||
val channelId = event.channel()
|
||||
fun consume(
|
||||
event: ChannelMetadataEvent,
|
||||
relay: Relay?,
|
||||
) {
|
||||
val channelId = event.channelId()
|
||||
// Log.d("MT", "New PublicChatMetadata ${event.channelInfo()}")
|
||||
if (channelId.isNullOrBlank()) return
|
||||
|
||||
@@ -1396,7 +1413,7 @@ object LocalCache {
|
||||
|
||||
val note = getOrCreateNote(event.id)
|
||||
if (note.event == null) {
|
||||
oldChannel.addNote(note)
|
||||
oldChannel.addNote(note, relay)
|
||||
note.loadEvent(event, author, emptyList())
|
||||
|
||||
refreshObservers(note)
|
||||
@@ -1407,14 +1424,14 @@ object LocalCache {
|
||||
event: ChannelMessageEvent,
|
||||
relay: Relay?,
|
||||
) {
|
||||
val channelId = event.channel()
|
||||
val channelId = event.channelId()
|
||||
|
||||
if (channelId.isNullOrBlank()) return
|
||||
|
||||
val channel = checkGetOrCreateChannel(channelId) ?: return
|
||||
|
||||
val note = getOrCreateNote(event.id)
|
||||
channel.addNote(note)
|
||||
channel.addNote(note, relay)
|
||||
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
@@ -1452,12 +1469,12 @@ object LocalCache {
|
||||
event: LiveActivitiesChatMessageEvent,
|
||||
relay: Relay?,
|
||||
) {
|
||||
val activityId = event.activity() ?: return
|
||||
val activityAddress = event.activityAddress() ?: return
|
||||
|
||||
val channel = getOrCreateChannel(activityId.toTag()) { LiveActivitiesChannel(activityId) }
|
||||
val channel = getOrCreateChannel(activityAddress.toValue()) { LiveActivitiesChannel(activityAddress) }
|
||||
|
||||
val note = getOrCreateNote(event.id)
|
||||
channel.addNote(note)
|
||||
channel.addNote(note, relay)
|
||||
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
|
||||
@@ -1845,6 +1862,21 @@ object LocalCache {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Will return true if supplied note is one of events to be excluded from
|
||||
* search results.
|
||||
*/
|
||||
private fun excludeNoteEventFromSearchResults(note: Note): Boolean =
|
||||
(
|
||||
note.event is GenericRepostEvent ||
|
||||
note.event is RepostEvent ||
|
||||
note.event is CommunityPostApprovalEvent ||
|
||||
note.event is ReactionEvent ||
|
||||
note.event is LnZapEvent ||
|
||||
note.event is LnZapRequestEvent ||
|
||||
note.event is FileHeaderEvent
|
||||
)
|
||||
|
||||
fun findNotesStartingWith(
|
||||
text: String,
|
||||
forAccount: Account,
|
||||
@@ -1855,19 +1887,13 @@ object LocalCache {
|
||||
|
||||
if (key != null) {
|
||||
val note = getNoteIfExists(key)
|
||||
if (note != null) {
|
||||
if ((note != null) && !excludeNoteEventFromSearchResults(note)) {
|
||||
return listOfNotNull(note)
|
||||
}
|
||||
}
|
||||
|
||||
return notes.filter { _, note ->
|
||||
if (note.event is GenericRepostEvent ||
|
||||
note.event is RepostEvent ||
|
||||
note.event is CommunityPostApprovalEvent ||
|
||||
note.event is ReactionEvent ||
|
||||
note.event is LnZapEvent ||
|
||||
note.event is LnZapRequestEvent
|
||||
) {
|
||||
if (excludeNoteEventFromSearchResults(note)) {
|
||||
return@filter false
|
||||
}
|
||||
|
||||
@@ -1892,13 +1918,7 @@ object LocalCache {
|
||||
return@filter false
|
||||
} +
|
||||
addressables.filter { _, addressable ->
|
||||
if (addressable.event is GenericRepostEvent ||
|
||||
addressable.event is RepostEvent ||
|
||||
addressable.event is CommunityPostApprovalEvent ||
|
||||
addressable.event is ReactionEvent ||
|
||||
addressable.event is LnZapEvent ||
|
||||
addressable.event is LnZapRequestEvent
|
||||
) {
|
||||
if (excludeNoteEventFromSearchResults(addressable)) {
|
||||
return@filter false
|
||||
}
|
||||
|
||||
@@ -2115,7 +2135,7 @@ object LocalCache {
|
||||
val noteEvent = note.event
|
||||
if (noteEvent is AddressableEvent) {
|
||||
noteEvent.createdAt <
|
||||
(addressables.get(noteEvent.address().toTag())?.event?.createdAt ?: 0)
|
||||
(addressables.get(noteEvent.aTag().toTag())?.event?.createdAt ?: 0)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -2124,7 +2144,7 @@ object LocalCache {
|
||||
val childrenToBeRemoved = mutableListOf<Note>()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
val newerVersion = (it.event as? AddressableEvent)?.address()?.toTag()?.let { tag -> addressables.get(tag) }
|
||||
val newerVersion = (it.event as? AddressableEvent)?.aTag()?.toTag()?.let { tag -> addressables.get(tag) }
|
||||
if (newerVersion != null) {
|
||||
it.moveAllReferencesTo(newerVersion)
|
||||
}
|
||||
@@ -2308,7 +2328,7 @@ object LocalCache {
|
||||
fun justVerify(event: Event): Boolean {
|
||||
checkNotInMainThread()
|
||||
|
||||
return if (!event.hasValidSignature()) {
|
||||
return if (!event.verify()) {
|
||||
try {
|
||||
event.checkSignature()
|
||||
} catch (e: Exception) {
|
||||
@@ -2391,9 +2411,9 @@ object LocalCache {
|
||||
}
|
||||
}
|
||||
is ChannelMessageEvent -> {
|
||||
draft.channel()?.let { channelId ->
|
||||
draft.channelId()?.let { channelId ->
|
||||
checkGetOrCreateChannel(channelId)?.let { channel ->
|
||||
channel.addNote(note)
|
||||
channel.addNote(note, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2460,7 +2480,7 @@ object LocalCache {
|
||||
}
|
||||
}
|
||||
is ChannelMessageEvent -> {
|
||||
draft.channel()?.let { channelId ->
|
||||
draft.channelId()?.let { channelId ->
|
||||
checkGetOrCreateChannel(channelId)?.let { channel ->
|
||||
channel.removeNote(draftWrap)
|
||||
}
|
||||
@@ -2519,11 +2539,11 @@ object LocalCache {
|
||||
is CalendarDateSlotEvent -> consume(event, relay)
|
||||
is CalendarTimeSlotEvent -> consume(event, relay)
|
||||
is CalendarRSVPEvent -> consume(event, relay)
|
||||
is ChannelCreateEvent -> consume(event)
|
||||
is ChannelCreateEvent -> consume(event, relay)
|
||||
is ChannelListEvent -> consume(event, relay)
|
||||
is ChannelHideMessageEvent -> consume(event)
|
||||
is ChannelMessageEvent -> consume(event, relay)
|
||||
is ChannelMetadataEvent -> consume(event)
|
||||
is ChannelMetadataEvent -> consume(event, relay)
|
||||
is ChannelMuteUserEvent -> consume(event)
|
||||
is ChatMessageEncryptedFileHeaderEvent -> consume(event, relay)
|
||||
is ChatMessageEvent -> consume(event, relay)
|
||||
|
||||
@@ -37,35 +37,41 @@ import com.vitorpamplona.ammolite.relays.BundledUpdate
|
||||
import com.vitorpamplona.ammolite.relays.Relay
|
||||
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.quartz.experimental.bounties.addedRewardValue
|
||||
import com.vitorpamplona.quartz.experimental.bounties.hasAdditionalReward
|
||||
import com.vitorpamplona.quartz.lightning.LnInvoiceUtil
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.EventReference
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.anyHashTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.isTaggedHash
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNAddr
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentRequestEvent
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import com.vitorpamplona.quartz.utils.containsAny
|
||||
@@ -80,11 +86,11 @@ import kotlin.coroutines.resume
|
||||
|
||||
@Stable
|
||||
class AddressableNote(
|
||||
val address: ATag,
|
||||
) : Note(address.toTag()) {
|
||||
override fun idNote() = address.toNAddr(relayHintUrl())
|
||||
val address: Address,
|
||||
) : Note(address.toValue()) {
|
||||
override fun idNote() = toNAddr()
|
||||
|
||||
override fun toNEvent() = address.toNAddr(relayHintUrl())
|
||||
override fun toNEvent() = toNAddr()
|
||||
|
||||
override fun idDisplayNote() = idNote().toShortenHex()
|
||||
|
||||
@@ -103,11 +109,15 @@ class AddressableNote(
|
||||
|
||||
override fun wasOrShouldBeDeletedBy(
|
||||
deletionEvents: Set<HexKey>,
|
||||
deletionAddressables: Set<ATag>,
|
||||
deletionAddressables: Set<Address>,
|
||||
): Boolean {
|
||||
val thisEvent = event
|
||||
return deletionAddressables.contains(address) || (thisEvent != null && deletionEvents.contains(thisEvent.id))
|
||||
}
|
||||
|
||||
fun toNAddr() = NAddress.create(address.kind, address.pubKeyHex, address.dTag, relayHintUrl())
|
||||
|
||||
fun toATag() = ATag(address, relayHintUrl())
|
||||
}
|
||||
|
||||
@Stable
|
||||
@@ -195,16 +205,16 @@ open class Note(
|
||||
event is LiveActivitiesChatMessageEvent ||
|
||||
event is LiveActivitiesEvent
|
||||
) {
|
||||
(event as? ChannelMessageEvent)?.channel()
|
||||
?: (event as? ChannelMetadataEvent)?.channel()
|
||||
(event as? ChannelMessageEvent)?.channelId()
|
||||
?: (event as? ChannelMetadataEvent)?.channelId()
|
||||
?: (event as? ChannelCreateEvent)?.id
|
||||
?: (event as? LiveActivitiesChatMessageEvent)?.activity()?.toTag()
|
||||
?: (event as? LiveActivitiesEvent)?.address()?.toTag()
|
||||
?: (event as? LiveActivitiesEvent)?.aTag()?.toTag()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
open fun address(): ATag? = null
|
||||
open fun address(): Address? = null
|
||||
|
||||
open fun createdAt() = event?.createdAt
|
||||
|
||||
@@ -442,6 +452,13 @@ open class Note(
|
||||
}
|
||||
}
|
||||
|
||||
fun addRelayBrief(brief: RelayBriefInfoCache.RelayBriefInfo) {
|
||||
if (brief !in relays) {
|
||||
addRelaySync(brief)
|
||||
flowSet?.relays?.invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun isPaidByCalculation(
|
||||
account: Account,
|
||||
zapEvents: List<Pair<Note, Note?>>,
|
||||
@@ -702,7 +719,7 @@ open class Note(
|
||||
|
||||
fun hasPledgeBy(user: User): Boolean =
|
||||
replies
|
||||
.filter { it.event?.isTaggedHash("bounty-added-reward") ?: false }
|
||||
.filter { it.event?.hasAdditionalReward() ?: false }
|
||||
.any {
|
||||
val pledgeValue =
|
||||
try {
|
||||
@@ -716,18 +733,7 @@ open class Note(
|
||||
pledgeValue != null && it.author == user
|
||||
}
|
||||
|
||||
fun pledgedAmountByOthers(): BigDecimal =
|
||||
replies
|
||||
.filter { it.event?.isTaggedHash("bounty-added-reward") ?: false }
|
||||
.mapNotNull {
|
||||
try {
|
||||
BigDecimal(it.event?.content)
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
null
|
||||
// do nothing if it can't convert to bigdecimal
|
||||
}
|
||||
}.sumOf { it }
|
||||
fun pledgedAmountByOthers(): BigDecimal = replies.sumOf { it.event?.addedRewardValue() ?: BigDecimal.ZERO }
|
||||
|
||||
fun hasAnyReports(): Boolean {
|
||||
val dayAgo = TimeUtils.oneDayAgo()
|
||||
@@ -839,7 +845,7 @@ open class Note(
|
||||
}
|
||||
|
||||
if (accountChoices.hiddenWordsCase.isNotEmpty()) {
|
||||
if (thisEvent is BaseTextNoteEvent && thisEvent.content.containsAny(accountChoices.hiddenWordsCase)) {
|
||||
if (thisEvent is BaseThreadedEvent && thisEvent.content.containsAny(accountChoices.hiddenWordsCase)) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -912,11 +918,41 @@ open class Note(
|
||||
|
||||
open fun wasOrShouldBeDeletedBy(
|
||||
deletionEvents: Set<HexKey>,
|
||||
deletionAddressables: Set<ATag>,
|
||||
deletionAddressables: Set<Address>,
|
||||
): Boolean {
|
||||
val thisEvent = event
|
||||
return deletionEvents.contains(idHex) || (thisEvent is AddressableEvent && deletionAddressables.contains(thisEvent.address()))
|
||||
}
|
||||
|
||||
fun toETag(): ETag {
|
||||
val noteEvent = event
|
||||
return if (noteEvent != null) {
|
||||
ETag(noteEvent.id, relayHintUrl(), noteEvent.pubKey)
|
||||
} else {
|
||||
ETag(idHex, relayHintUrl(), author?.pubkeyHex)
|
||||
}
|
||||
}
|
||||
|
||||
fun toEId(): EventReference {
|
||||
val noteEvent = event
|
||||
return if (noteEvent != null) {
|
||||
// uses the confirmed event id if available
|
||||
EventReference(noteEvent.id, noteEvent.pubKey, relayHintUrl())
|
||||
} else {
|
||||
EventReference(idHex, author?.pubkeyHex, relayHintUrl())
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Event> toEventHint() = (event as? T)?.let { EventHintBundle(it, relayHintUrl(), author?.bestRelayHint()) }
|
||||
|
||||
fun toMarkedETag(marker: MarkedETag.MARKER): MarkedETag {
|
||||
val noteEvent = event
|
||||
return if (noteEvent != null) {
|
||||
MarkedETag(noteEvent.id, relayHintUrl(), marker, noteEvent.pubKey)
|
||||
} else {
|
||||
MarkedETag(idHex, relayHintUrl(), marker, author?.pubkeyHex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
class ParticipantListBuilder {
|
||||
private fun addFollowsThatDirectlyParticipateOnToSet(
|
||||
|
||||
@@ -35,6 +35,7 @@ data class Settings(
|
||||
val dontShowPushNotificationSelector: Boolean = false,
|
||||
val dontAskForNotificationPermissions: Boolean = false,
|
||||
val featureSet: FeatureSetType = FeatureSetType.SIMPLIFIED,
|
||||
val gallerySet: ProfileGalleryType = ProfileGalleryType.CLASSIC,
|
||||
)
|
||||
|
||||
enum class ThemeType(
|
||||
@@ -75,6 +76,14 @@ enum class FeatureSetType(
|
||||
PERFORMANCE(2, R.string.ui_feature_set_type_performance),
|
||||
}
|
||||
|
||||
enum class ProfileGalleryType(
|
||||
val screenCode: Int,
|
||||
val resourceId: Int,
|
||||
) {
|
||||
CLASSIC(0, R.string.gallery_type_classic),
|
||||
MODERN(1, R.string.gallery_type_modern),
|
||||
}
|
||||
|
||||
fun parseConnectivityType(code: Boolean?): ConnectivityType =
|
||||
when (code) {
|
||||
ConnectivityType.ALWAYS.prefCode -> ConnectivityType.ALWAYS
|
||||
@@ -105,6 +114,15 @@ fun parseFeatureSetType(screenCode: Int): FeatureSetType =
|
||||
}
|
||||
}
|
||||
|
||||
fun parseGalleryType(screenCode: Int): ProfileGalleryType =
|
||||
when (screenCode) {
|
||||
ProfileGalleryType.CLASSIC.screenCode -> ProfileGalleryType.CLASSIC
|
||||
ProfileGalleryType.MODERN.screenCode -> ProfileGalleryType.MODERN
|
||||
else -> {
|
||||
ProfileGalleryType.CLASSIC
|
||||
}
|
||||
}
|
||||
|
||||
enum class BooleanType(
|
||||
val prefCode: Boolean?,
|
||||
val screenCode: Int,
|
||||
|
||||
@@ -23,7 +23,7 @@ package com.vitorpamplona.amethyst.model
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import kotlinx.collections.immutable.ImmutableSet
|
||||
@@ -132,7 +132,7 @@ class ThreadAssembler {
|
||||
}
|
||||
|
||||
class OnlyLatestVersionSet : MutableSet<Note> {
|
||||
val map = hashMapOf<ATag, Long>()
|
||||
val map = hashMapOf<Address, Long>()
|
||||
val set = hashSetOf<Note>()
|
||||
|
||||
override fun add(element: Note): Boolean {
|
||||
@@ -149,7 +149,7 @@ class OnlyLatestVersionSet : MutableSet<Note> {
|
||||
}
|
||||
|
||||
private fun innerAdd(
|
||||
address: ATag,
|
||||
address: Address,
|
||||
element: Note,
|
||||
loadedCreatedAt: Long,
|
||||
): Boolean {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import java.lang.Long.min
|
||||
|
||||
@@ -32,15 +32,16 @@ import com.vitorpamplona.ammolite.relays.BundledUpdate
|
||||
import com.vitorpamplona.ammolite.relays.Relay
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.quartz.lightning.Lud06
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.UserMetadata
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.isTaggedGeoHash
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.isTaggedHash
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
|
||||
@@ -88,14 +89,15 @@ class User(
|
||||
|
||||
fun pubkeyDisplayHex() = pubkeyNpub().toShortenHex()
|
||||
|
||||
fun toNProfile(): String {
|
||||
val relayList = (LocalCache.getAddressableNoteIfExists(AdvertisedRelayListEvent.createAddressTag(pubkeyHex))?.event as? AdvertisedRelayListEvent)?.writeRelays()
|
||||
fun authorRelayList() = (LocalCache.getAddressableNoteIfExists(AdvertisedRelayListEvent.createAddressTag(pubkeyHex))?.event as? AdvertisedRelayListEvent)
|
||||
|
||||
return NProfile.create(
|
||||
pubkeyHex,
|
||||
relayList?.take(3) ?: listOfNotNull(latestMetadataRelay),
|
||||
)
|
||||
}
|
||||
fun toNProfile() = NProfile.create(pubkeyHex, relayHints())
|
||||
|
||||
fun relayHints() = authorRelayList()?.writeRelays()?.take(3) ?: listOfNotNull(latestMetadataRelay)
|
||||
|
||||
fun bestRelayHint() = authorRelayList()?.writeRelays()?.firstOrNull() ?: latestMetadataRelay
|
||||
|
||||
fun toPTag() = PTag(pubkeyHex, bestRelayHint())
|
||||
|
||||
fun toNostrUri() = "nostr:${toNProfile()}"
|
||||
|
||||
@@ -240,16 +242,13 @@ class User(
|
||||
}.flatten()
|
||||
|
||||
@Synchronized
|
||||
private fun getOrCreatePrivateChatroomSync(key: ChatroomKey): Chatroom {
|
||||
checkNotInMainThread()
|
||||
|
||||
return privateChatrooms[key]
|
||||
private fun getOrCreatePrivateChatroomSync(key: ChatroomKey): Chatroom =
|
||||
privateChatrooms[key]
|
||||
?: run {
|
||||
val privateChatroom = Chatroom()
|
||||
privateChatrooms = privateChatrooms + Pair(key, privateChatroom)
|
||||
privateChatroom
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOrCreatePrivateChatroom(user: User): Chatroom {
|
||||
val key = ChatroomKey(persistentSetOf(user.pubkeyHex))
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ abstract class AmethystNostrDataSource(
|
||||
val note = LocalCache.getNoteIfExists(eventId)
|
||||
val noteEvent = note?.event
|
||||
if (noteEvent is AddressableEvent) {
|
||||
LocalCache.getAddressableNoteIfExists(noteEvent.address().toTag())?.addRelay(relay)
|
||||
LocalCache.getAddressableNoteIfExists(noteEvent.aTag().toTag())?.addRelay(relay)
|
||||
} else {
|
||||
note?.addRelay(relay)
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ import coil3.key.Keyer
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.Options
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser.Companion.base64contentPattern
|
||||
import com.vitorpamplona.quartz.CryptoUtils
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.utils.sha256.sha256
|
||||
import java.util.Base64
|
||||
|
||||
@Stable
|
||||
@@ -83,7 +83,7 @@ class Base64Fetcher(
|
||||
options: Options,
|
||||
): String? =
|
||||
if (data.scheme == "data") {
|
||||
CryptoUtils.sha256(data.toString().toByteArray()).toHexKey()
|
||||
sha256(data.toString().toByteArray()).toHexKey()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
|
||||
|
||||
fun String.isUTF16Char(pos: Int): Boolean = Character.charCount(this.codePointAt(pos)) == 2
|
||||
@@ -125,5 +126,9 @@ fun String.firstFullCharOrEmoji(tags: ImmutableListOfLists<String>): String {
|
||||
}
|
||||
}
|
||||
|
||||
if (EmojiCoder.isCoded(this)) {
|
||||
return EmojiCoder.cropToFirstMessage(this)
|
||||
}
|
||||
|
||||
return firstFullChar()
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.fonfon.kgeohash.GeoHash
|
||||
import com.fonfon.kgeohash.toGeoHash
|
||||
import com.vitorpamplona.amethyst.service.LocationState.Companion.MIN_DISTANCE
|
||||
import com.vitorpamplona.amethyst.service.LocationState.Companion.MIN_TIME
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeohashPrecision
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
@@ -125,7 +126,7 @@ class LocationState(
|
||||
(context)
|
||||
.get(MIN_TIME, MIN_DISTANCE)
|
||||
.map {
|
||||
LocationResult.Success(it.toGeoHash(com.vitorpamplona.amethyst.ui.actions.GeohashPrecision.KM_5_X_5.digits)) as LocationResult
|
||||
LocationResult.Success(it.toGeoHash(GeohashPrecision.KM_5_X_5.digits)) as LocationResult
|
||||
}.onEach {
|
||||
latestLocation = it
|
||||
}.catch { e ->
|
||||
|
||||
+11
-11
@@ -35,21 +35,21 @@ import com.vitorpamplona.quartz.experimental.edits.PrivateOutboxRelayListEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackSelectionEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitIssueEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitPatchEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitReplyEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.selection.EmojiPackSelectionEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.reply.GitReplyEvent
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
|
||||
import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
|
||||
@@ -64,12 +64,12 @@ import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeAwardEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeProfilesEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.FileServersEvent
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
// TODO: Migrate this to a property of AccountVi
|
||||
|
||||
@@ -27,8 +27,8 @@ import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
|
||||
object NostrChannelDataSource : AmethystNostrDataSource("ChatroomFeed") {
|
||||
var account: Account? = null
|
||||
|
||||
+2
-2
@@ -25,8 +25,8 @@ import com.vitorpamplona.amethyst.service.relays.EOSEAccount
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
|
||||
object NostrChatroomDataSource : AmethystNostrDataSource("ChatroomFeed") {
|
||||
lateinit var account: Account
|
||||
|
||||
+4
-4
@@ -26,10 +26,10 @@ import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
|
||||
object NostrChatroomListDataSource : AmethystNostrDataSource("MailBoxFeed") {
|
||||
lateinit var account: Account
|
||||
|
||||
+4
-4
@@ -24,8 +24,8 @@ import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
|
||||
object NostrCommunityDataSource : AmethystNostrDataSource("SingleCommunityFeed") {
|
||||
private var communityToWatch: AddressableNote? = null
|
||||
@@ -38,7 +38,7 @@ object NostrCommunityDataSource : AmethystNostrDataSource("SingleCommunityFeed")
|
||||
val authors =
|
||||
community
|
||||
.moderators()
|
||||
.map { it.key }
|
||||
.map { it.pubKey }
|
||||
.plus(listOfNotNull(myCommunityToWatch.author?.pubkeyHex))
|
||||
|
||||
if (authors.isEmpty()) return null
|
||||
@@ -50,7 +50,7 @@ object NostrCommunityDataSource : AmethystNostrDataSource("SingleCommunityFeed")
|
||||
authors = authors,
|
||||
tags =
|
||||
mapOf(
|
||||
"a" to listOf(myCommunityToWatch.address.toTag()),
|
||||
"a" to listOf(myCommunityToWatch.address.toValue()),
|
||||
),
|
||||
kinds = listOf(CommunityPostApprovalEvent.KIND),
|
||||
limit = 500,
|
||||
|
||||
+8
-8
@@ -27,14 +27,14 @@ import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SinceAuthorPerRelayFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
@@ -23,13 +23,13 @@ package com.vitorpamplona.amethyst.service
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
|
||||
@@ -23,15 +23,15 @@ package com.vitorpamplona.amethyst.service
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
|
||||
@@ -27,22 +27,22 @@ import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SinceAuthorPerRelayFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PinListEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
+12
-13
@@ -24,19 +24,19 @@ import com.vitorpamplona.ammolite.relays.ALL_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
||||
@@ -44,19 +44,18 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
|
||||
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.nip19Bech32.parse
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.pack.EmojiPackEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PinListEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
@@ -81,7 +80,7 @@ object NostrSearchEventOrUserDataSource : AmethystNostrDataSource("SearchEventFe
|
||||
}
|
||||
|
||||
when (val parsed = Nip19Parser.uriToRoute(mySearchString)?.entity) {
|
||||
is NSec -> KeyPair(privKey = parsed.hex.bechToBytes()).pubKey.toHexKey()
|
||||
is NSec -> Nip01.pubKeyCreate(parsed.hex.hexToByteArray()).toHexKey()
|
||||
is NPub -> parsed.hex
|
||||
is NProfile -> parsed.hex
|
||||
is com.vitorpamplona.quartz.nip19Bech32.entities.Note -> parsed.hex
|
||||
|
||||
+2
-2
@@ -27,8 +27,8 @@ import com.vitorpamplona.ammolite.relays.EVENT_FINDER_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
|
||||
object NostrSingleChannelDataSource : AmethystNostrDataSource("SingleChannelFeed") {
|
||||
private var channelsToWatch = setOf<Channel>()
|
||||
|
||||
+5
-5
@@ -35,12 +35,12 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitReplyEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.reply.GitReplyEvent
|
||||
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
|
||||
|
||||
@@ -81,7 +81,7 @@ object NostrSingleEventDataSource : AmethystNostrDataSource("SingleEventFeed") {
|
||||
CommunityPostApprovalEvent.KIND,
|
||||
LiveActivitiesChatMessageEvent.KIND,
|
||||
),
|
||||
tags = mapOf("a" to it.mapNotNull { it.address()?.toTag() }),
|
||||
tags = mapOf("a" to it.mapNotNull { it.address()?.toValue() }),
|
||||
since = findMinimumEOSEs(it),
|
||||
// Max amount of "replies" to download on a specific event.
|
||||
limit = 1000,
|
||||
@@ -95,7 +95,7 @@ object NostrSingleEventDataSource : AmethystNostrDataSource("SingleEventFeed") {
|
||||
listOf(
|
||||
DeletionEvent.KIND,
|
||||
),
|
||||
tags = mapOf("a" to it.mapNotNull { it.address()?.toTag() }),
|
||||
tags = mapOf("a" to it.mapNotNull { it.address()?.toValue() }),
|
||||
since = findMinimumEOSEs(it),
|
||||
// Max amount of "replies" to download on a specific event.
|
||||
limit = 10,
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.EOSETime
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.relationshipStatus.RelationshipStatusEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
|
||||
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
|
||||
+4
-4
@@ -24,12 +24,12 @@ import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
@@ -49,7 +49,7 @@ import com.vitorpamplona.quartz.nip68Picture.PictureEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
||||
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.AppRecommendationEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.recommendation.AppRecommendationEvent
|
||||
|
||||
object NostrUserProfileDataSource : AmethystNostrDataSource("UserProfileFeed") {
|
||||
var user: User? = null
|
||||
|
||||
@@ -27,7 +27,7 @@ import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SinceAuthorPerRelayFilter
|
||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||
import com.vitorpamplona.quartz.experimental.nip95.FileStorageHeaderEvent
|
||||
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.VideoVerticalEvent
|
||||
|
||||
@@ -25,7 +25,7 @@ import android.util.LruCache
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.BuildConfig
|
||||
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
||||
import com.vitorpamplona.quartz.CryptoUtils
|
||||
import com.vitorpamplona.quartz.utils.RandomInstance
|
||||
import okhttp3.EventListener
|
||||
import okhttp3.Protocol
|
||||
import okhttp3.Request
|
||||
@@ -70,7 +70,7 @@ object OnlineChecker {
|
||||
.url(url.replace("wss+livekit://", "wss://"))
|
||||
.header("Upgrade", "websocket")
|
||||
.header("Connection", "Upgrade")
|
||||
.header("Sec-WebSocket-Key", CryptoUtils.random(16).toByteString().base64())
|
||||
.header("Sec-WebSocket-Key", RandomInstance.bytes(16).toByteString().base64())
|
||||
.header("Sec-WebSocket-Version", "13")
|
||||
.header("Sec-WebSocket-Extensions", "permessage-deflate")
|
||||
.build()
|
||||
|
||||
@@ -32,14 +32,14 @@ import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource.user
|
||||
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceErrorResponse
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.splits.BaseZapSplitSetup
|
||||
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetup
|
||||
import com.vitorpamplona.quartz.nip57Zaps.splits.ZapSplitSetupLnAddress
|
||||
import com.vitorpamplona.quartz.nip57Zaps.splits.zapSplitSetup
|
||||
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -77,7 +77,7 @@ class ZapPaymentHandler(
|
||||
if (!zapSplitSetup.isNullOrEmpty()) {
|
||||
zapSplitSetup
|
||||
} else if (noteEvent is LiveActivitiesEvent && noteEvent.hasHost()) {
|
||||
noteEvent.hosts().map { ZapSplitSetup(it.pubKeyHex, it.relay, weight = 1.0) }
|
||||
noteEvent.hosts().map { ZapSplitSetup(it.pubKey, it.relayHint, weight = 1.0) }
|
||||
} else if (noteEvent is AppDefinitionEvent) {
|
||||
val appLud16 = noteEvent.appMetaData()?.lnAddress()
|
||||
if (appLud16 != null) {
|
||||
|
||||
+7
-7
@@ -37,18 +37,18 @@ import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEncryptedFileHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUserIds
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.ChatMessageEncryptedFileHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||
import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
|
||||
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
|
||||
import com.vitorpamplona.quartz.nip55AndroidSigner.NostrSignerExternal
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.math.BigDecimal
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
@@ -137,7 +137,7 @@ class EventNotificationConsumer(
|
||||
Log.d(TAG, "New Notification Arrived")
|
||||
if (!LocalCache.justVerify(event)) return
|
||||
|
||||
val users = event.taggedUsers().map { LocalCache.getOrCreateUser(it) }
|
||||
val users = event.taggedUserIds().map { LocalCache.getOrCreateUser(it) }
|
||||
val npubs = users.map { it.pubkeyNpub() }.toSet()
|
||||
|
||||
// PushNotification Wraps don't include a receiver.
|
||||
|
||||
+57
-23
@@ -20,51 +20,85 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.okhttp
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.quartz.nip17Dm.AESGCM
|
||||
import com.vitorpamplona.quartz.nip17Dm.NostrCipher
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.encryption.AESGCM
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
|
||||
class EncryptedBlobInterceptor(
|
||||
val cache: EncryptionKeyCache,
|
||||
) : Interceptor {
|
||||
fun Response.decrypt(cipher: NostrCipher): Response {
|
||||
private fun Response.decryptOrNullWithErrorCorrection(info: DecryptInformation): Response? {
|
||||
val body = peekBody(Long.MAX_VALUE)
|
||||
val decryptedBytes = cipher.decrypt(body.bytes())
|
||||
val newBody = decryptedBytes.toResponseBody(body.contentType())
|
||||
return newBuilder().body(newBody).build()
|
||||
}
|
||||
|
||||
fun Response.decryptOrNull(cipher: NostrCipher): Response? =
|
||||
try {
|
||||
decrypt(cipher)
|
||||
} catch (e: Exception) {
|
||||
Log.w("EncryptedBlobInterceptor", "Failed to decrypt", e)
|
||||
null
|
||||
// Only tries to decrypt if the content-type is a byte array
|
||||
if (body.contentType().toString() != "application/octet-stream") {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun Response.decryptOrNullWithErrorCorrection(cipher: NostrCipher): Response? {
|
||||
return decryptOrNull(cipher) ?: return if (cipher is AESGCM) {
|
||||
decryptOrNull(cipher.copyUsingUTF8Nonce())
|
||||
} else {
|
||||
null
|
||||
val bytes = body.bytes()
|
||||
|
||||
// Tries the correct way first
|
||||
// if it fails, tries to decrypt as UTF8 nonce, which was how
|
||||
// 0xChat started encrypting
|
||||
val decrypted =
|
||||
info.cipher.decryptOrNull(bytes) ?: if (info.cipher is AESGCM) {
|
||||
info.cipher.copyUsingUTF8Nonce().decryptOrNull(bytes)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
if (decrypted == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return newBuilder()
|
||||
.apply {
|
||||
body(
|
||||
decrypted.toResponseBody(
|
||||
info.mimeType?.toMediaTypeOrNull() ?: body.contentType(),
|
||||
),
|
||||
)
|
||||
// removes hints that would make the app requrest partial byte arrays
|
||||
// in videos, which are impossible to decrypt.
|
||||
removeHeader("accept-ranges")
|
||||
// Fixes the size of the body array
|
||||
header("content-length", decrypted.size.toString())
|
||||
// Trusts the mimetype from the event is better than the mimetype from the server
|
||||
info.mimeType?.let { header("content-type", it) }
|
||||
}.build()
|
||||
}
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val response = chain.proceed(request)
|
||||
val encryptionKeys = cache.get(request.url.toString())
|
||||
|
||||
val cipher = cache.get(request.url.toString()) ?: return response
|
||||
// We cannot use Range requests (partial byte arrays)
|
||||
// in encrypted payloads because we won't be able to
|
||||
// decrypt partial byte arrays.
|
||||
val newRequest =
|
||||
if (encryptionKeys != null) {
|
||||
request
|
||||
.newBuilder()
|
||||
.removeHeader("Range")
|
||||
.build()
|
||||
} else {
|
||||
request
|
||||
}
|
||||
|
||||
val response = chain.proceed(newRequest)
|
||||
|
||||
if (encryptionKeys == null) {
|
||||
return response
|
||||
}
|
||||
|
||||
if (response.isSuccessful) {
|
||||
return response.decryptOrNullWithErrorCorrection(cipher) ?: response
|
||||
return response.decryptOrNullWithErrorCorrection(encryptionKeys) ?: response
|
||||
} else {
|
||||
// Log redirections to be able to use the cipher.
|
||||
response.header("Location")?.let {
|
||||
cache.add(it, cipher)
|
||||
cache.add(it, encryptionKeys)
|
||||
}
|
||||
}
|
||||
return response
|
||||
|
||||
+19
-8
@@ -21,7 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.service.okhttp
|
||||
|
||||
import android.util.LruCache
|
||||
import com.vitorpamplona.quartz.nip17Dm.NostrCipher
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
|
||||
|
||||
/**
|
||||
* Neigther ExoPlayer, nor Coil support passing key and nonce to the Interceptor via
|
||||
@@ -30,16 +30,27 @@ import com.vitorpamplona.quartz.nip17Dm.NostrCipher
|
||||
* This class serves as a key cache to decrypt the body of HTTP calls that need it.
|
||||
*/
|
||||
class EncryptionKeyCache {
|
||||
val cache = LruCache<String, NostrCipher>(100)
|
||||
val cache = LruCache<String, DecryptInformation>(100)
|
||||
|
||||
fun add(
|
||||
url: String?,
|
||||
decryptInformation: DecryptInformation,
|
||||
) {
|
||||
if (cache.get(url) == null) {
|
||||
cache.put(url, decryptInformation)
|
||||
}
|
||||
}
|
||||
|
||||
fun add(
|
||||
url: String?,
|
||||
cipher: NostrCipher,
|
||||
) {
|
||||
if (cache.get(url) == null) {
|
||||
cache.put(url, cipher)
|
||||
}
|
||||
}
|
||||
expectedMimeType: String?,
|
||||
) = add(url, DecryptInformation(cipher, expectedMimeType))
|
||||
|
||||
fun get(url: String): NostrCipher? = cache.get(url)
|
||||
fun get(url: String): DecryptInformation? = cache.get(url)
|
||||
}
|
||||
|
||||
class DecryptInformation(
|
||||
val cipher: NostrCipher,
|
||||
val mimeType: String?,
|
||||
)
|
||||
|
||||
+3
-2
@@ -21,7 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.service.okhttp
|
||||
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.quartz.nip17Dm.NostrCipher
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
|
||||
import okhttp3.OkHttpClient
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
@@ -124,5 +124,6 @@ object HttpClientManager {
|
||||
fun addCipherToCache(
|
||||
url: String,
|
||||
cipher: NostrCipher,
|
||||
) = cache.add(url, cipher)
|
||||
expectedMimeType: String?,
|
||||
) = cache.add(url, cipher, expectedMimeType)
|
||||
}
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.okhttp
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relays.sockets.WebSocket
|
||||
import com.vitorpamplona.quartz.nip01Core.relays.sockets.WebSocketListener
|
||||
import com.vitorpamplona.quartz.nip01Core.relays.sockets.WebsocketBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.relays.sockets.WebsocketBuilderFactory
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebSocket
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebSocketListener
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebsocketBuilder
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebsocketBuilderFactory
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ package com.vitorpamplona.amethyst.service.uploads
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import com.vitorpamplona.quartz.CryptoUtils
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.NostrCipher
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
|
||||
import com.vitorpamplona.quartz.utils.sha256.sha256
|
||||
import java.io.File
|
||||
|
||||
class EncryptFilesResult(
|
||||
@@ -48,9 +48,9 @@ class EncryptFiles {
|
||||
|
||||
resolver.openInputStream(inputFile)!!.use { inputStream ->
|
||||
val bytes = inputStream.readBytes()
|
||||
val originalHash = CryptoUtils.sha256(bytes).toHexKey()
|
||||
val originalHash = sha256(bytes).toHexKey()
|
||||
val encrypted = cipher.encrypt(bytes)
|
||||
val encryptedHash = CryptoUtils.sha256(encrypted).toHexKey()
|
||||
val encryptedHash = sha256(encrypted).toHexKey()
|
||||
|
||||
encryptedFile.outputStream().use { outputStream ->
|
||||
outputStream.write(encrypted)
|
||||
|
||||
@@ -30,9 +30,9 @@ import android.os.Build
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.amethyst.commons.blurhash.toBlurhash
|
||||
import com.vitorpamplona.amethyst.service.Blurhash
|
||||
import com.vitorpamplona.quartz.CryptoUtils
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import com.vitorpamplona.quartz.utils.sha256.sha256
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import java.io.IOException
|
||||
|
||||
@@ -40,7 +40,7 @@ class FileHeader(
|
||||
val mimeType: String?,
|
||||
val hash: String,
|
||||
val size: Int,
|
||||
val dim: Dimension?,
|
||||
val dim: DimensionTag?,
|
||||
val blurHash: Blurhash?,
|
||||
) {
|
||||
class UnableToDownload(
|
||||
@@ -51,7 +51,7 @@ class FileHeader(
|
||||
suspend fun prepare(
|
||||
fileUrl: String,
|
||||
mimeType: String?,
|
||||
dimPrecomputed: Dimension?,
|
||||
dimPrecomputed: DimensionTag?,
|
||||
forceProxy: Boolean,
|
||||
): Result<FileHeader> =
|
||||
try {
|
||||
@@ -71,10 +71,10 @@ class FileHeader(
|
||||
fun prepare(
|
||||
data: ByteArray,
|
||||
mimeType: String?,
|
||||
dimPrecomputed: Dimension?,
|
||||
dimPrecomputed: DimensionTag?,
|
||||
): Result<FileHeader> =
|
||||
try {
|
||||
val hash = CryptoUtils.sha256(data).toHexKey()
|
||||
val hash = sha256(data).toHexKey()
|
||||
val size = data.size
|
||||
|
||||
val (blurHash, dim) =
|
||||
@@ -82,7 +82,7 @@ class FileHeader(
|
||||
val opt = BitmapFactory.Options()
|
||||
opt.inPreferredConfig = Bitmap.Config.ARGB_8888
|
||||
val mBitmap = BitmapFactory.decodeByteArray(data, 0, data.size, opt)
|
||||
Pair(Blurhash(mBitmap.toBlurhash()), Dimension(mBitmap.width, mBitmap.height))
|
||||
Pair(Blurhash(mBitmap.toBlurhash()), DimensionTag(mBitmap.width, mBitmap.height))
|
||||
} else if (mimeType?.startsWith("video/") == true) {
|
||||
val mediaMetadataRetriever = MediaMetadataRetriever()
|
||||
mediaMetadataRetriever.setDataSource(ByteArrayMediaDataSource(data))
|
||||
@@ -135,12 +135,12 @@ fun MediaMetadataRetriever.getThumbnail(): Bitmap? {
|
||||
}
|
||||
}
|
||||
|
||||
fun MediaMetadataRetriever.prepareDimFromVideo(): Dimension? {
|
||||
fun MediaMetadataRetriever.prepareDimFromVideo(): DimensionTag? {
|
||||
val width = prepareVideoWidth() ?: return null
|
||||
val height = prepareVideoHeight() ?: return null
|
||||
|
||||
return if (width > 0 && height > 0) {
|
||||
Dimension(width, height)
|
||||
DimensionTag(width, height)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
+3
-3
@@ -20,8 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.uploads
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
|
||||
data class MediaUploadResult(
|
||||
// A publicly accessible URL to the BUD-01 GET /<sha256> endpoint (optionally with a file extension)
|
||||
@@ -35,7 +35,7 @@ data class MediaUploadResult(
|
||||
// upload time
|
||||
val uploaded: Long? = null,
|
||||
// dimensions
|
||||
val dimension: Dimension? = null,
|
||||
val dimension: DimensionTag? = null,
|
||||
// magnet link
|
||||
val magnet: String? = null,
|
||||
// info hash
|
||||
|
||||
+6
-6
@@ -26,7 +26,7 @@ import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing
|
||||
import com.vitorpamplona.quartz.nip17Dm.NostrCipher
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.joinAll
|
||||
@@ -49,7 +49,7 @@ class MultiOrchestrator(
|
||||
suspend fun upload(
|
||||
scope: CoroutineScope,
|
||||
alt: String?,
|
||||
sensitiveContent: Boolean,
|
||||
contentWarningReason: String?,
|
||||
mediaQuality: CompressorQuality,
|
||||
server: ServerName,
|
||||
account: Account,
|
||||
@@ -62,7 +62,7 @@ class MultiOrchestrator(
|
||||
item.media.uri,
|
||||
item.media.mimeType,
|
||||
alt,
|
||||
sensitiveContent,
|
||||
contentWarningReason,
|
||||
mediaQuality,
|
||||
server,
|
||||
account,
|
||||
@@ -79,7 +79,7 @@ class MultiOrchestrator(
|
||||
suspend fun uploadEncrypted(
|
||||
scope: CoroutineScope,
|
||||
alt: String?,
|
||||
sensitiveContent: Boolean,
|
||||
contentWarningReason: String?,
|
||||
mediaQuality: CompressorQuality,
|
||||
cipher: NostrCipher,
|
||||
server: ServerName,
|
||||
@@ -93,7 +93,7 @@ class MultiOrchestrator(
|
||||
item.media.uri,
|
||||
item.media.mimeType,
|
||||
alt,
|
||||
sensitiveContent,
|
||||
contentWarningReason,
|
||||
mediaQuality,
|
||||
cipher,
|
||||
server,
|
||||
@@ -126,5 +126,5 @@ class MultiOrchestrator(
|
||||
|
||||
fun size() = list.size
|
||||
|
||||
fun get(index: Int) = list.get(index)
|
||||
fun get(index: Int) = list[index]
|
||||
}
|
||||
|
||||
+11
-11
@@ -29,7 +29,7 @@ import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
|
||||
import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader
|
||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
|
||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
||||
import com.vitorpamplona.quartz.nip17Dm.NostrCipher
|
||||
import com.vitorpamplona.quartz.nip17Dm.files.encryption.NostrCipher
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
@@ -132,7 +132,7 @@ class UploadOrchestrator {
|
||||
contentType: String?,
|
||||
size: Long?,
|
||||
alt: String?,
|
||||
sensitiveContent: Boolean,
|
||||
contentWarningReason: String?,
|
||||
serverBaseUrl: String,
|
||||
contentTypeForResult: String?,
|
||||
originalHash: String?,
|
||||
@@ -147,7 +147,7 @@ class UploadOrchestrator {
|
||||
contentType = contentType,
|
||||
size = size,
|
||||
alt = alt,
|
||||
sensitiveContent = if (sensitiveContent) "" else null,
|
||||
sensitiveContent = contentWarningReason,
|
||||
serverBaseUrl = serverBaseUrl,
|
||||
forceProxy = account::shouldUseTorForNIP96,
|
||||
onProgress = { percent: Float ->
|
||||
@@ -175,7 +175,7 @@ class UploadOrchestrator {
|
||||
contentType: String?,
|
||||
size: Long?,
|
||||
alt: String?,
|
||||
sensitiveContent: Boolean,
|
||||
contentWarningReason: String?,
|
||||
serverBaseUrl: String,
|
||||
contentTypeForResult: String?,
|
||||
originalHash: String?,
|
||||
@@ -191,7 +191,7 @@ class UploadOrchestrator {
|
||||
contentType = contentType,
|
||||
size = size,
|
||||
alt = alt,
|
||||
sensitiveContent = if (sensitiveContent) "" else null,
|
||||
sensitiveContent = contentWarningReason,
|
||||
serverBaseUrl = serverBaseUrl,
|
||||
forceProxy = account::shouldUseTorForNIP96,
|
||||
httpAuth = account::createBlossomUploadAuth,
|
||||
@@ -292,7 +292,7 @@ class UploadOrchestrator {
|
||||
uri: Uri,
|
||||
mimeType: String?,
|
||||
alt: String?,
|
||||
sensitiveContent: Boolean,
|
||||
contentWarningReason: String?,
|
||||
compressionQuality: CompressorQuality,
|
||||
server: ServerName,
|
||||
account: Account,
|
||||
@@ -302,8 +302,8 @@ class UploadOrchestrator {
|
||||
|
||||
return when (server.type) {
|
||||
ServerType.NIP95 -> uploadNIP95(compressed.uri, compressed.contentType, null, null, context)
|
||||
ServerType.NIP96 -> uploadNIP96(compressed.uri, compressed.contentType, compressed.size, alt, sensitiveContent, server.baseUrl, null, null, account, context)
|
||||
ServerType.Blossom -> uploadBlossom(compressed.uri, compressed.contentType, compressed.size, alt, sensitiveContent, server.baseUrl, null, null, account, context)
|
||||
ServerType.NIP96 -> uploadNIP96(compressed.uri, compressed.contentType, compressed.size, alt, contentWarningReason, server.baseUrl, null, null, account, context)
|
||||
ServerType.Blossom -> uploadBlossom(compressed.uri, compressed.contentType, compressed.size, alt, contentWarningReason, server.baseUrl, null, null, account, context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +311,7 @@ class UploadOrchestrator {
|
||||
uri: Uri,
|
||||
mimeType: String?,
|
||||
alt: String?,
|
||||
sensitiveContent: Boolean,
|
||||
contentWarningReason: String?,
|
||||
compressionQuality: CompressorQuality,
|
||||
encrypt: NostrCipher,
|
||||
server: ServerName,
|
||||
@@ -323,8 +323,8 @@ class UploadOrchestrator {
|
||||
|
||||
return when (server.type) {
|
||||
ServerType.NIP95 -> uploadNIP95(encrypted.uri, encrypted.contentType, compressed.contentType, encrypted.originalHash, context)
|
||||
ServerType.NIP96 -> uploadNIP96(encrypted.uri, encrypted.contentType, encrypted.size, alt, sensitiveContent, server.baseUrl, compressed.contentType, encrypted.originalHash, account, context)
|
||||
ServerType.Blossom -> uploadBlossom(encrypted.uri, encrypted.contentType, encrypted.size, alt, sensitiveContent, server.baseUrl, compressed.contentType, encrypted.originalHash, account, context)
|
||||
ServerType.NIP96 -> uploadNIP96(encrypted.uri, encrypted.contentType, encrypted.size, alt, contentWarningReason, server.baseUrl, compressed.contentType, encrypted.originalHash, account, context)
|
||||
ServerType.Blossom -> uploadBlossom(encrypted.uri, encrypted.contentType, encrypted.size, alt, contentWarningReason, server.baseUrl, compressed.contentType, encrypted.originalHash, account, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -35,10 +35,10 @@ import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
||||
import com.vitorpamplona.amethyst.service.uploads.MediaUploadResult
|
||||
import com.vitorpamplona.amethyst.service.uploads.nip96.randomChars
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.CryptoUtils
|
||||
import com.vitorpamplona.quartz.blossom.BlossomAuthorizationEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.utils.sha256.sha256
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
@@ -88,7 +88,7 @@ class BlossomUploader {
|
||||
|
||||
checkNotNull(payload) { "Can't open the image input stream" }
|
||||
|
||||
val hash = CryptoUtils.sha256(payload).toHexKey()
|
||||
val hash = sha256(payload).toHexKey()
|
||||
|
||||
val imageInputStream = contentResolver.openInputStream(uri)
|
||||
|
||||
|
||||
+15
-16
@@ -34,13 +34,12 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
||||
import com.vitorpamplona.amethyst.service.uploads.MediaUploadResult
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip36SensitiveContent.CONTENT_WARNING
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.AuthToken
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.Nip96Result
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.PartialEvent
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.ResultParser
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.ServerInfo
|
||||
import com.vitorpamplona.quartz.nip36SensitiveContent.ContentWarningTag
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.actions.DeleteResult
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.actions.PartialEvent
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.actions.UploadResult
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.info.ServerInfo
|
||||
import com.vitorpamplona.quartz.nip98HttpAuth.HTTPAuthorizationEvent
|
||||
import kotlinx.coroutines.delay
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
@@ -153,7 +152,7 @@ class Nip96Uploader {
|
||||
.addFormDataPart("size", length.toString())
|
||||
.also { body ->
|
||||
alt?.ifBlank { null }?.let { body.addFormDataPart("alt", it) }
|
||||
sensitiveContent?.let { body.addFormDataPart(CONTENT_WARNING, it) }
|
||||
sensitiveContent?.let { body.addFormDataPart(ContentWarningTag.TAG_NAME, it) }
|
||||
contentType?.let { body.addFormDataPart("content_type", it) }
|
||||
}.addFormDataPart(
|
||||
"file",
|
||||
@@ -169,7 +168,7 @@ class Nip96Uploader {
|
||||
},
|
||||
).build()
|
||||
|
||||
httpAuth(server.apiUrl, "POST", null)?.let { requestBuilder.addHeader("Authorization", AuthToken().encodeAuth(it)) }
|
||||
httpAuth(server.apiUrl, "POST", null)?.let { requestBuilder.addHeader("Authorization", it.toAuthToken()) }
|
||||
|
||||
requestBuilder
|
||||
.addHeader("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
|
||||
@@ -181,7 +180,7 @@ class Nip96Uploader {
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (response.isSuccessful) {
|
||||
response.body.use { body ->
|
||||
val result = ResultParser().parseResults(body.string())
|
||||
val result = UploadResult.parse(body.string())
|
||||
if (!result.processingUrl.isNullOrBlank()) {
|
||||
return waitProcessing(result, server, forceProxy, onProgress)
|
||||
} else if (result.status == "success") {
|
||||
@@ -244,7 +243,7 @@ class Nip96Uploader {
|
||||
?.firstOrNull { it.size > 1 && it[0] == "dim" }
|
||||
?.get(1)
|
||||
?.ifBlank { null }
|
||||
?.let { Dimension.parse(it) }
|
||||
?.let { DimensionTag.parse(it) }
|
||||
val magnet =
|
||||
nip96.tags
|
||||
?.firstOrNull { it.size > 1 && it[0] == "magnet" }
|
||||
@@ -265,7 +264,7 @@ class Nip96Uploader {
|
||||
contentType: String?,
|
||||
server: ServerInfo,
|
||||
forceProxy: (String) -> Boolean,
|
||||
httpAuth: (String, String, ByteArray?) -> HTTPAuthorizationEvent,
|
||||
httpAuth: (String, String, ByteArray?) -> HTTPAuthorizationEvent?,
|
||||
context: Context,
|
||||
): Boolean {
|
||||
val extension =
|
||||
@@ -275,7 +274,7 @@ class Nip96Uploader {
|
||||
|
||||
val requestBuilder = Request.Builder()
|
||||
|
||||
httpAuth(server.apiUrl, "DELETE", null)?.let { requestBuilder.addHeader("Authorization", AuthToken().encodeAuth(it)) }
|
||||
httpAuth(server.apiUrl, "DELETE", null)?.let { requestBuilder.addHeader("Authorization", it.toAuthToken()) }
|
||||
|
||||
val request =
|
||||
requestBuilder
|
||||
@@ -287,7 +286,7 @@ class Nip96Uploader {
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (response.isSuccessful) {
|
||||
response.body.use { body ->
|
||||
val result = ResultParser().parseDeleteResults(body.string())
|
||||
val result = DeleteResult.parse(body.string())
|
||||
return result.status == "success"
|
||||
}
|
||||
} else {
|
||||
@@ -302,7 +301,7 @@ class Nip96Uploader {
|
||||
}
|
||||
|
||||
private suspend fun waitProcessing(
|
||||
result: Nip96Result,
|
||||
result: UploadResult,
|
||||
server: ServerInfo,
|
||||
forceProxy: (String) -> Boolean,
|
||||
onProgress: (percentage: Float) -> Unit,
|
||||
@@ -324,7 +323,7 @@ class Nip96Uploader {
|
||||
val client = HttpClientManager.getHttpClient(forceProxy(procUrl))
|
||||
client.newCall(request).execute().use {
|
||||
if (it.isSuccessful) {
|
||||
it.body.use { currentResult = ResultParser().parseResults(it.string()) }
|
||||
it.body.use { currentResult = UploadResult.parse(it.string()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.service.uploads.nip96
|
||||
import android.util.Log
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.ServerInfo
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.ServerInfoParser
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.info.ServerInfo
|
||||
import com.vitorpamplona.quartz.nip96FileStorage.info.ServerInfoParser
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import okhttp3.Request
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ import com.vitorpamplona.amethyst.ui.screen.AccountScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
||||
import com.vitorpamplona.amethyst.ui.tor.TorManager
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed
|
||||
@@ -55,12 +55,12 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.Note
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -46,10 +46,19 @@ import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
|
||||
import com.vitorpamplona.quartz.experimental.nip95.FileStorageEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
|
||||
import com.vitorpamplona.quartz.nip92IMeta.IMetaTagBuilder
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.alt
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.blurhash
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.dims
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.hash
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.magnet
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.mimeType
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.originalHash
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.sensitiveContent
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.size
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -165,7 +174,7 @@ open class EditPostViewModel : ViewModel() {
|
||||
myMultiOrchestrator.upload(
|
||||
viewModelScope,
|
||||
alt,
|
||||
sensitiveContent,
|
||||
if (sensitiveContent) "" else null,
|
||||
MediaCompressor.intToCompressorQuality(mediaQuality),
|
||||
server,
|
||||
myAccount,
|
||||
@@ -175,7 +184,12 @@ open class EditPostViewModel : ViewModel() {
|
||||
if (results.allGood) {
|
||||
results.successful.forEach { state ->
|
||||
if (state.result is UploadOrchestrator.OrchestratorResult.NIP95Result) {
|
||||
account?.createNip95(state.result.bytes, headerInfo = state.result.fileHeader, alt, sensitiveContent) { nip95 ->
|
||||
account?.createNip95(
|
||||
state.result.bytes,
|
||||
headerInfo = state.result.fileHeader,
|
||||
alt,
|
||||
if (sensitiveContent) "" else null,
|
||||
) { nip95 ->
|
||||
nip95attachments = nip95attachments + nip95
|
||||
val note = nip95.let { it1 -> account?.consumeNip95(it1.first, it1.second) }
|
||||
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ import com.vitorpamplona.amethyst.ui.note.SearchIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChannelName
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.ChannelName
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchBarViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
|
||||
@@ -58,17 +58,18 @@ object MediaSaverToDisk {
|
||||
if (videoUri != null) {
|
||||
if (!videoUri.startsWith("file")) {
|
||||
downloadAndSave(
|
||||
url = videoUri,
|
||||
mimeType = mimeType,
|
||||
context = localContext,
|
||||
forceProxy = forceProxy,
|
||||
url = videoUri,
|
||||
onSuccess = onSuccess,
|
||||
onError = onError,
|
||||
)
|
||||
} else {
|
||||
save(
|
||||
context = localContext,
|
||||
localFile = videoUri.toUri().toFile(),
|
||||
mimeType = mimeType,
|
||||
context = localContext,
|
||||
onSuccess = onSuccess,
|
||||
onError = onError,
|
||||
)
|
||||
@@ -83,6 +84,7 @@ object MediaSaverToDisk {
|
||||
*/
|
||||
fun downloadAndSave(
|
||||
url: String,
|
||||
mimeType: String?,
|
||||
forceProxy: Boolean,
|
||||
context: Context,
|
||||
onSuccess: () -> Any?,
|
||||
@@ -121,9 +123,16 @@ object MediaSaverToDisk {
|
||||
val contentType = response.header("Content-Type")
|
||||
checkNotNull(contentType) { "Can't find out the content type" }
|
||||
|
||||
val realType =
|
||||
if (mimeType != null && contentType == "application/octet-stream") {
|
||||
mimeType
|
||||
} else {
|
||||
contentType
|
||||
}
|
||||
|
||||
saveContentQ(
|
||||
displayName = File(url).nameWithoutExtension,
|
||||
contentType = contentType,
|
||||
contentType = realType,
|
||||
contentSource = response.body.source(),
|
||||
contentResolver = context.contentResolver,
|
||||
)
|
||||
|
||||
+41
-12
@@ -26,6 +26,10 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -53,19 +57,44 @@ class NewChannelViewModel : ViewModel() {
|
||||
fun create() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
account?.let { account ->
|
||||
if (originalChannel == null) {
|
||||
account.sendCreateNewChannel(
|
||||
channelName.value.text,
|
||||
channelDescription.value.text,
|
||||
channelPicture.value.text,
|
||||
)
|
||||
val channel = originalChannel
|
||||
if (channel == null) {
|
||||
val template =
|
||||
ChannelCreateEvent.build(
|
||||
channelName.value.text,
|
||||
channelDescription.value.text,
|
||||
channelPicture.value.text,
|
||||
null,
|
||||
)
|
||||
|
||||
account.sendCreateNewChannel(template)
|
||||
} else {
|
||||
account.sendChangeChannel(
|
||||
channelName.value.text,
|
||||
channelDescription.value.text,
|
||||
channelPicture.value.text,
|
||||
originalChannel!!,
|
||||
)
|
||||
val event = channel.event
|
||||
|
||||
val template =
|
||||
if (event != null) {
|
||||
val hint = EventHintBundle<ChannelCreateEvent>(event, channel.relays().firstOrNull())
|
||||
|
||||
ChannelMetadataEvent.build(
|
||||
channelName.value.text,
|
||||
channelDescription.value.text,
|
||||
channelPicture.value.text,
|
||||
null,
|
||||
hint,
|
||||
)
|
||||
} else {
|
||||
val eTag = ETag(channel.idHex, channel.relays().firstOrNull())
|
||||
|
||||
ChannelMetadataEvent.build(
|
||||
channelName.value.text,
|
||||
channelDescription.value.text,
|
||||
channelPicture.value.text,
|
||||
null,
|
||||
eTag,
|
||||
)
|
||||
}
|
||||
|
||||
account.sendChangeChannel(template)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ open class NewMediaModel : ViewModel() {
|
||||
myMultiOrchestrator.upload(
|
||||
viewModelScope,
|
||||
caption,
|
||||
sensitiveContent,
|
||||
if (sensitiveContent) "" else null,
|
||||
MediaCompressor.intToCompressorQuality(mediaQualitySlider),
|
||||
serverToUse,
|
||||
myAccount,
|
||||
@@ -140,7 +140,7 @@ open class NewMediaModel : ViewModel() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
withTimeoutOrNull(30000) {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
account?.createNip95(it.bytes, headerInfo = it.fileHeader, caption, sensitiveContent) { nip95 ->
|
||||
account?.createNip95(it.bytes, headerInfo = it.fileHeader, caption, if (sensitiveContent) "" else null) { nip95 ->
|
||||
account?.consumeAndSendNip95(nip95.first, nip95.second, relayList)
|
||||
continuation.resume(true)
|
||||
}
|
||||
@@ -160,7 +160,7 @@ open class NewMediaModel : ViewModel() {
|
||||
it.magnet,
|
||||
it.fileHeader,
|
||||
caption,
|
||||
sensitiveContent,
|
||||
if (sensitiveContent) "" else null,
|
||||
it.uploadedHash,
|
||||
relayList,
|
||||
) {
|
||||
@@ -180,7 +180,7 @@ open class NewMediaModel : ViewModel() {
|
||||
account?.sendAllAsOnePictureEvent(
|
||||
imageUrls,
|
||||
caption,
|
||||
sensitiveContent,
|
||||
if (sensitiveContent) "" else null,
|
||||
relayList,
|
||||
) {
|
||||
continuation.resume(true)
|
||||
|
||||
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.ui.actions
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.Nip01
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
import com.vitorpamplona.quartz.nip19Bech32.bech32.Bech32
|
||||
import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
|
||||
@@ -180,7 +180,7 @@ class NewMessageTagger(
|
||||
val restOfWord = key.substring(63)
|
||||
// Converts to npub
|
||||
val pubkey =
|
||||
Nip19Parser.uriToRoute(KeyPair(privKey = keyB32.bechToBytes()).pubKey.toNpub()) ?: return null
|
||||
Nip19Parser.uriToRoute(Nip01.pubKeyCreate(keyB32.bechToBytes()).toNpub()) ?: return null
|
||||
|
||||
return DirtyKeyInfo(pubkey, restOfWord.ifEmpty { null })
|
||||
} else if (key.startsWith("npub1", true)) {
|
||||
|
||||
@@ -52,7 +52,7 @@ fun NewPollClosing(pollViewModel: NewPostViewModel) {
|
||||
pollViewModel.isValidClosedAt.value = true
|
||||
if (text.isNotEmpty()) {
|
||||
try {
|
||||
val int = text.toInt()
|
||||
val int = text.toLong()
|
||||
if (int < 0) {
|
||||
pollViewModel.isValidClosedAt.value = false
|
||||
} else {
|
||||
|
||||
+523
-330
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -142,7 +142,7 @@ fun NewUserMetadataScreen(
|
||||
modifier = Modifier.padding(10.dp).verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringRes(R.string.display_name)) },
|
||||
label = { Text(text = stringRes(R.string.profile_name)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.displayName.value,
|
||||
onValueChange = { postViewModel.displayName.value = it },
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ import androidx.compose.ui.text.input.TransformedText
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKey
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
|
||||
+1
@@ -41,4 +41,5 @@ val DEFAULT_MEDIA_SERVERS: List<ServerName> =
|
||||
ServerName("Satellite (Paid)", "https://cdn.satellite.earth", ServerType.Blossom),
|
||||
ServerName("NostrCheck.me (Blossom)", "https://cdn.nostrcheck.me", ServerType.Blossom),
|
||||
ServerName("Nostr.Download", "https://nostr.download", ServerType.Blossom),
|
||||
ServerName("NostrMedia (Paid)", "https://nostrmedia.com", ServerType.Blossom),
|
||||
)
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.grayText
|
||||
import com.vitorpamplona.ammolite.relays.Constants
|
||||
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
|
||||
|
||||
@Composable
|
||||
fun AllRelayListView(
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.ui.actions.relays
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
|
||||
|
||||
@Immutable
|
||||
data class BasicRelaySetupInfo(
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ import com.vitorpamplona.ammolite.relays.Constants
|
||||
import com.vitorpamplona.ammolite.relays.Constants.activeTypesGlobalChats
|
||||
import com.vitorpamplona.ammolite.relays.FeedType
|
||||
import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache
|
||||
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
|
||||
import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.allGoodColor
|
||||
import com.vitorpamplona.amethyst.ui.theme.largeRelayIconModifier
|
||||
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
|
||||
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ import com.vitorpamplona.amethyst.ui.note.UserCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.nip01Core.relays.RelayStat
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.RelayStat
|
||||
import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -44,7 +44,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -69,7 +68,7 @@ import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.CashuCardBorders
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size18Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.SmallishBorder
|
||||
@@ -151,11 +150,7 @@ fun CashuPreviewNew(
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
|
||||
Card(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 10.dp)
|
||||
.clip(shape = QuoteBorder),
|
||||
modifier = CashuCardBorders,
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
|
||||
@@ -63,11 +63,11 @@ import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadChannel
|
||||
import com.vitorpamplona.amethyst.ui.note.njumpLink
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed
|
||||
@@ -77,7 +77,7 @@ 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.nip19Bech32.toNIP19
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip30CustomEmoji.CustomEmoji
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.RepeatMode
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathEffect
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.StrokeJoin
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun AnimatedBorderTextCornerRadius(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = Color.Unspecified,
|
||||
textAlign: TextAlign? = null,
|
||||
fontSize: TextUnit = 12.sp,
|
||||
) {
|
||||
val infiniteTransition = rememberInfiniteTransition()
|
||||
val animatedFloatRestart =
|
||||
infiniteTransition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 100f,
|
||||
animationSpec =
|
||||
infiniteRepeatable(
|
||||
animation = tween(5000, easing = LinearEasing),
|
||||
repeatMode = RepeatMode.Restart,
|
||||
),
|
||||
)
|
||||
|
||||
Text(
|
||||
text = text,
|
||||
fontSize = fontSize,
|
||||
modifier =
|
||||
modifier
|
||||
.drawBehind {
|
||||
val brush =
|
||||
Brush.sweepGradient(
|
||||
colors = listOf(Color.Cyan, Color.Magenta, Color.Yellow),
|
||||
)
|
||||
|
||||
drawRoundRect(
|
||||
brush = brush,
|
||||
style =
|
||||
Stroke(
|
||||
width = 2.dp.toPx(),
|
||||
cap = StrokeCap.Round,
|
||||
join = StrokeJoin.Round,
|
||||
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), animatedFloatRestart.value),
|
||||
),
|
||||
cornerRadius =
|
||||
androidx.compose.ui.geometry
|
||||
.CornerRadius(6.dp.toPx()),
|
||||
)
|
||||
}.padding(3.dp),
|
||||
color = color,
|
||||
textAlign = textAlign,
|
||||
)
|
||||
}
|
||||
|
||||
// Example usage in a composable function:
|
||||
@Composable
|
||||
@Preview
|
||||
fun ExampleAnimatedBorder() {
|
||||
Column {
|
||||
AnimatedBorderTextCornerRadius(text = "Rounded Corners", Modifier)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -62,6 +63,7 @@ import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.em
|
||||
import com.vitorpamplona.amethyst.commons.compose.produceCachedState
|
||||
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||
import com.vitorpamplona.amethyst.commons.richtext.Base64Segment
|
||||
import com.vitorpamplona.amethyst.commons.richtext.BechSegment
|
||||
import com.vitorpamplona.amethyst.commons.richtext.CashuSegment
|
||||
@@ -77,6 +79,7 @@ import com.vitorpamplona.amethyst.commons.richtext.PhoneSegment
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment
|
||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
|
||||
import com.vitorpamplona.amethyst.commons.richtext.SchemelessUrlSegment
|
||||
import com.vitorpamplona.amethyst.commons.richtext.SecretEmoji
|
||||
import com.vitorpamplona.amethyst.commons.richtext.Segment
|
||||
import com.vitorpamplona.amethyst.commons.richtext.WithdrawSegment
|
||||
import com.vitorpamplona.amethyst.model.HashtagIcon
|
||||
@@ -91,13 +94,16 @@ import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.list.LoadUser
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.CashuCardBorders
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.inlinePlaceholder
|
||||
import com.vitorpamplona.amethyst.ui.theme.innerPostModifier
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
fun isMarkdown(content: String): Boolean =
|
||||
content.startsWith("> ") ||
|
||||
@@ -375,6 +381,7 @@ private fun RenderWordWithoutPreview(
|
||||
is WithdrawSegment -> Text(word.segmentText)
|
||||
is CashuSegment -> Text(word.segmentText)
|
||||
is EmailSegment -> ClickableEmail(word.segmentText)
|
||||
is SecretEmoji -> Text(word.segmentText)
|
||||
is PhoneSegment -> ClickablePhone(word.segmentText)
|
||||
is BechSegment -> BechLink(word.segmentText, false, 0, backgroundColor, accountViewModel, nav)
|
||||
is HashTagSegment -> HashTag(word, nav)
|
||||
@@ -403,6 +410,7 @@ private fun RenderWordWithPreview(
|
||||
is WithdrawSegment -> MayBeWithdrawal(word.segmentText, accountViewModel)
|
||||
is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
|
||||
is EmailSegment -> ClickableEmail(word.segmentText)
|
||||
is SecretEmoji -> DisplaySecretEmoji(word, state, callbackUri, true, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||
is PhoneSegment -> ClickablePhone(word.segmentText)
|
||||
is BechSegment -> BechLink(word.segmentText, true, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||
is HashTagSegment -> HashTag(word, nav)
|
||||
@@ -510,6 +518,101 @@ fun DisplayFullNote(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplaySecretEmoji(
|
||||
segment: SecretEmoji,
|
||||
state: RichTextViewerState,
|
||||
callbackUri: String?,
|
||||
canPreview: Boolean,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
if (canPreview && quotesLeft > 0) {
|
||||
var secretContent by remember {
|
||||
mutableStateOf<RichTextViewerState?>(null)
|
||||
}
|
||||
|
||||
var showPopup by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
LaunchedEffect(segment) {
|
||||
launch(Dispatchers.Default) {
|
||||
secretContent =
|
||||
CachedRichTextParser.parseText(
|
||||
EmojiCoder.decode(segment.segmentText),
|
||||
state.tags,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val localSecretContent = secretContent
|
||||
|
||||
AnimatedBorderTextCornerRadius(
|
||||
segment.segmentText,
|
||||
Modifier.clickable {
|
||||
showPopup = !showPopup
|
||||
},
|
||||
)
|
||||
|
||||
if (localSecretContent != null && showPopup) {
|
||||
CoreSecretMessage(localSecretContent, callbackUri, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||
}
|
||||
} else {
|
||||
Text(segment.segmentText)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun CoreSecretMessage(
|
||||
localSecretContent: RichTextViewerState,
|
||||
callbackUri: String?,
|
||||
quotesLeft: Int,
|
||||
backgroundColor: MutableState<Color>,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
if (localSecretContent.paragraphs.size == 1) {
|
||||
localSecretContent.paragraphs[0].words.forEach { word ->
|
||||
RenderWordWithPreview(
|
||||
word,
|
||||
localSecretContent,
|
||||
backgroundColor,
|
||||
quotesLeft,
|
||||
callbackUri,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
} else if (localSecretContent.paragraphs.size > 1) {
|
||||
val spaceWidth = measureSpaceWidth(LocalTextStyle.current)
|
||||
|
||||
Column(CashuCardBorders) {
|
||||
localSecretContent.paragraphs.forEach { paragraph ->
|
||||
FlowRow(
|
||||
modifier = Modifier.align(if (paragraph.isRTL) Alignment.End else Alignment.Start),
|
||||
horizontalArrangement = Arrangement.spacedBy(spaceWidth),
|
||||
) {
|
||||
paragraph.words.forEach { word ->
|
||||
RenderWordWithPreview(
|
||||
word,
|
||||
localSecretContent,
|
||||
backgroundColor,
|
||||
quotesLeft,
|
||||
callbackUri,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HashTag(
|
||||
segment: HashTagSegment,
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||
import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons
|
||||
import com.vitorpamplona.amethyst.commons.hashtags.Lightning
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun SecretEmojiRequest(onSuccess: (String) -> Unit) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CustomHashTagIcons.Lightning,
|
||||
null,
|
||||
modifier = Size20Modifier,
|
||||
tint = Color.Unspecified,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringRes(R.string.secret_emoji_maker),
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W500,
|
||||
modifier = Modifier.padding(start = 10.dp),
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
|
||||
var secretMessage by remember { mutableStateOf("") }
|
||||
var publicPrefix by remember { mutableStateOf("") }
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringRes(R.string.secret_note_to_receiver)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = secretMessage,
|
||||
onValueChange = { secretMessage = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.secret_note_to_receiver_placeholder),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
),
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text(text = stringRes(R.string.secret_visible_text)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = publicPrefix,
|
||||
onValueChange = { publicPrefix = it },
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.secret_visible_text_placeholder),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
singleLine = true,
|
||||
)
|
||||
|
||||
Button(
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 10.dp),
|
||||
onClick = {
|
||||
onSuccess(EmojiCoder.encode(publicPrefix, secretMessage))
|
||||
},
|
||||
shape = QuoteBorder,
|
||||
colors =
|
||||
ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.secret_add_to_text),
|
||||
color = Color.White,
|
||||
fontSize = 20.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.selection.LocalTextSelectionColors
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.TextFieldColors
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.takeOrElse
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
// COPIED FROM TEXT FIELD
|
||||
// The only change is the contentPadding below
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ThinPaddingTextField(
|
||||
value: TextFieldValue,
|
||||
onValueChange: (TextFieldValue) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
textStyle: TextStyle = LocalTextStyle.current,
|
||||
label: @Composable (() -> Unit)? = null,
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
prefix: @Composable (() -> Unit)? = null,
|
||||
suffix: @Composable (() -> Unit)? = null,
|
||||
supportingText: @Composable (() -> Unit)? = null,
|
||||
isError: Boolean = false,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
singleLine: Boolean = false,
|
||||
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
|
||||
minLines: Int = 1,
|
||||
interactionSource: MutableInteractionSource? = null,
|
||||
shape: Shape = TextFieldDefaults.shape,
|
||||
colors: TextFieldColors = TextFieldDefaults.colors(),
|
||||
// new fields
|
||||
contentPadding: PaddingValues =
|
||||
if (label == null) {
|
||||
TextFieldDefaults.contentPaddingWithoutLabel(
|
||||
start = 10.dp,
|
||||
top = 12.dp,
|
||||
end = 10.dp,
|
||||
bottom = 12.dp,
|
||||
)
|
||||
} else {
|
||||
TextFieldDefaults.contentPaddingWithLabel(
|
||||
start = 10.dp,
|
||||
top = 12.dp,
|
||||
end = 10.dp,
|
||||
bottom = 12.dp,
|
||||
)
|
||||
},
|
||||
) {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val interactionSource = interactionSource ?: remember { MutableInteractionSource() }
|
||||
|
||||
// If color is not provided via the text style, use content color as a default
|
||||
val textColor =
|
||||
textStyle.color.takeOrElse {
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
|
||||
// this has changed, but only because of private access on the original
|
||||
when {
|
||||
!enabled -> MaterialTheme.colorScheme.placeholderText
|
||||
isError -> MaterialTheme.colorScheme.onSurface
|
||||
focused -> MaterialTheme.colorScheme.onSurface
|
||||
else -> MaterialTheme.colorScheme.onSurface
|
||||
}
|
||||
}
|
||||
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
|
||||
|
||||
CompositionLocalProvider(LocalTextSelectionColors provides colors.textSelectionColors) {
|
||||
BasicTextField(
|
||||
value = value,
|
||||
modifier =
|
||||
modifier
|
||||
.defaultMinSize(
|
||||
minWidth = TextFieldDefaults.MinWidth,
|
||||
minHeight = 36.dp, // this has changed
|
||||
),
|
||||
onValueChange = onValueChange,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
textStyle = mergedTextStyle,
|
||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), // this has changed
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
interactionSource = interactionSource,
|
||||
singleLine = singleLine,
|
||||
maxLines = maxLines,
|
||||
minLines = minLines,
|
||||
decorationBox =
|
||||
@Composable { innerTextField ->
|
||||
TextFieldDefaults.DecorationBox(
|
||||
value = value.text,
|
||||
visualTransformation = visualTransformation,
|
||||
innerTextField = innerTextField,
|
||||
placeholder = placeholder,
|
||||
label = label,
|
||||
leadingIcon = leadingIcon,
|
||||
trailingIcon = trailingIcon,
|
||||
prefix = prefix,
|
||||
suffix = suffix,
|
||||
supportingText = supportingText,
|
||||
shape = shape,
|
||||
singleLine = singleLine,
|
||||
enabled = enabled,
|
||||
isError = isError,
|
||||
interactionSource = interactionSource,
|
||||
colors = colors,
|
||||
contentPadding = contentPadding, // this has changed
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -119,8 +119,8 @@ import com.vitorpamplona.amethyst.ui.theme.Size75dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.VolumeBottomIconSize
|
||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.videoGalleryModifier
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.tags.WaveformTag
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@@ -208,10 +208,10 @@ fun VideoView(
|
||||
roundedCorner: Boolean,
|
||||
gallery: Boolean = false,
|
||||
contentScale: ContentScale,
|
||||
waveform: ImmutableList<Int>? = null,
|
||||
waveform: WaveformTag? = null,
|
||||
artworkUri: String? = null,
|
||||
authorName: String? = null,
|
||||
dimensions: Dimension? = null,
|
||||
dimensions: DimensionTag? = null,
|
||||
blurhash: String? = null,
|
||||
nostrUriCallback: String? = null,
|
||||
onDialog: ((Boolean) -> Unit)? = null,
|
||||
@@ -239,10 +239,10 @@ fun VideoView(
|
||||
thumb: VideoThumb? = null,
|
||||
borderModifier: Modifier,
|
||||
contentScale: ContentScale,
|
||||
waveform: ImmutableList<Int>? = null,
|
||||
waveform: WaveformTag? = null,
|
||||
artworkUri: String? = null,
|
||||
authorName: String? = null,
|
||||
dimensions: Dimension? = null,
|
||||
dimensions: DimensionTag? = null,
|
||||
blurhash: String? = null,
|
||||
nostrUriCallback: String? = null,
|
||||
onDialog: ((Boolean) -> Unit)? = null,
|
||||
@@ -355,7 +355,7 @@ fun VideoViewInner(
|
||||
showControls: Boolean = true,
|
||||
contentScale: ContentScale,
|
||||
borderModifier: Modifier,
|
||||
waveform: ImmutableList<Int>? = null,
|
||||
waveform: WaveformTag? = null,
|
||||
artworkUri: String? = null,
|
||||
authorName: String? = null,
|
||||
nostrUriCallback: String? = null,
|
||||
@@ -732,7 +732,7 @@ private fun RenderVideoPlayer(
|
||||
showControls: Boolean = true,
|
||||
contentScale: ContentScale,
|
||||
nostrUriCallback: String?,
|
||||
waveform: ImmutableList<Int>? = null,
|
||||
waveform: WaveformTag? = null,
|
||||
keepPlaying: MutableState<Boolean>,
|
||||
automaticallyStartPlayback: State<Boolean>,
|
||||
activeOnScreen: MutableState<Boolean>,
|
||||
@@ -863,7 +863,7 @@ private fun pollCurrentDuration(controller: MediaController) =
|
||||
|
||||
@Composable
|
||||
fun Waveform(
|
||||
waveform: ImmutableList<Int>,
|
||||
waveform: WaveformTag,
|
||||
controller: MediaController,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
@@ -897,13 +897,13 @@ fun Waveform(
|
||||
|
||||
@Composable
|
||||
fun DrawWaveform(
|
||||
waveform: ImmutableList<Int>,
|
||||
waveform: WaveformTag,
|
||||
waveformProgress: MutableFloatState,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
AudioWaveformReadOnly(
|
||||
modifier = modifier.padding(start = 10.dp, end = 10.dp),
|
||||
amplitudes = waveform,
|
||||
amplitudes = waveform.wave,
|
||||
progress = waveformProgress.floatValue,
|
||||
progressBrush =
|
||||
Brush.infiniteLinearGradient(
|
||||
|
||||
+1
@@ -308,6 +308,7 @@ private fun saveMediaToGallery(
|
||||
|
||||
MediaSaverToDisk.downloadAndSave(
|
||||
content.url,
|
||||
mimeType = content.mimeType,
|
||||
forceProxy = useTor,
|
||||
localContext,
|
||||
onSuccess = {
|
||||
|
||||
+35
-9
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -93,11 +94,11 @@ import com.vitorpamplona.amethyst.ui.theme.Size30dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size75dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.hashVerifierMark
|
||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||
import com.vitorpamplona.quartz.CryptoUtils
|
||||
import com.vitorpamplona.quartz.nip01Core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import com.vitorpamplona.quartz.utils.sha256.sha256
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -264,7 +265,9 @@ fun LocalImageView(
|
||||
)
|
||||
}
|
||||
} else {
|
||||
DisplayUrlWithLoadingSymbol(content)
|
||||
WaitAndDisplay {
|
||||
DisplayUrlWithLoadingSymbol(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
is AsyncImagePainter.State.Error -> {
|
||||
@@ -370,7 +373,9 @@ fun UrlImageView(
|
||||
)
|
||||
}
|
||||
} else {
|
||||
DisplayUrlWithLoadingSymbol(content)
|
||||
WaitAndDisplay {
|
||||
DisplayUrlWithLoadingSymbol(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
is AsyncImagePainter.State.Error -> {
|
||||
@@ -514,12 +519,33 @@ fun ShowHash(content: MediaUrlContent) {
|
||||
verifiedHash?.let { HashVerificationSymbol(it) }
|
||||
}
|
||||
|
||||
fun aspectRatio(dim: Dimension?): Float? {
|
||||
fun aspectRatio(dim: DimensionTag?): Float? {
|
||||
if (dim == null) return null
|
||||
|
||||
return dim.width.toFloat() / dim.height.toFloat()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WaitAndDisplay(
|
||||
content:
|
||||
@Composable()
|
||||
(AnimatedVisibilityScope.() -> Unit),
|
||||
) {
|
||||
val visible = remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
delay(200)
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = visible.value,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DisplayUrlWithLoadingSymbol(content: BaseMediaContent) {
|
||||
val uri = LocalUriHandler.current
|
||||
@@ -641,7 +667,7 @@ fun ShareImageAction(
|
||||
videoUri: String?,
|
||||
postNostrUri: String?,
|
||||
blurhash: String?,
|
||||
dim: Dimension?,
|
||||
dim: DimensionTag?,
|
||||
hash: String?,
|
||||
mimeType: String?,
|
||||
onDismiss: () -> Unit,
|
||||
@@ -679,7 +705,7 @@ fun ShareImageAction(
|
||||
if (videoUri != null) {
|
||||
val n19 = Nip19Parser.uriToRoute(postNostrUri)?.entity as? NEvent
|
||||
if (n19 != null) {
|
||||
accountViewModel.addMediaToGallery(n19.hex, videoUri, n19.relay[0], blurhash, dim, hash, mimeType) // TODO Whole list or first?
|
||||
accountViewModel.addMediaToGallery(n19.hex, videoUri, n19.relay.getOrNull(0), blurhash, dim, hash, mimeType) // TODO Whole list or first?
|
||||
accountViewModel.toast(R.string.media_added, R.string.media_added_to_profile_gallery)
|
||||
}
|
||||
}
|
||||
@@ -695,7 +721,7 @@ private suspend fun verifyHash(content: MediaUrlContent): Boolean? {
|
||||
if (content.hash == null) return null
|
||||
|
||||
Amethyst.instance.coilCache.openSnapshot(content.url)?.use { snapshot ->
|
||||
val hash = CryptoUtils.sha256(snapshot.data.toFile().readBytes()).toHexKey()
|
||||
val hash = sha256(snapshot.data.toFile().readBytes()).toHexKey()
|
||||
|
||||
Log.d("Image Hash Verification", "$hash == ${content.hash}")
|
||||
|
||||
|
||||
+4
-5
@@ -48,8 +48,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoadedBechLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font17SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size17Modifier
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
|
||||
@@ -57,11 +55,12 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
|
||||
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.nip92IMeta.IMetaTag
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class MarkdownMediaRenderer(
|
||||
val startOfText: String,
|
||||
val tags: ImmutableListOfLists<String>?,
|
||||
val imetaByUrl: Map<String, IMetaTag>,
|
||||
val canPreview: Boolean,
|
||||
val quotesLeft: Int,
|
||||
val backgroundColor: MutableState<Color>,
|
||||
@@ -94,7 +93,7 @@ class MarkdownMediaRenderer(
|
||||
val content =
|
||||
parser.createMediaContent(
|
||||
fullUrl = uri,
|
||||
eventTags = tags ?: EmptyTagList,
|
||||
eventTags = imetaByUrl,
|
||||
description = title?.ifEmpty { null } ?: startOfText,
|
||||
) ?: MediaUrlImage(url = uri, description = title?.ifEmpty { null } ?: startOfText)
|
||||
|
||||
@@ -116,7 +115,7 @@ class MarkdownMediaRenderer(
|
||||
uri: String,
|
||||
richTextStringBuilder: RichTextString.Builder,
|
||||
) {
|
||||
val content = parser.createMediaContent(uri, eventTags = tags ?: EmptyTagList, startOfText, callbackUri)
|
||||
val content = parser.createMediaContent(uri, imetaByUrl, startOfText, callbackUri)
|
||||
|
||||
if (canPreview) {
|
||||
if (content != null) {
|
||||
|
||||
+3
-2
@@ -49,10 +49,11 @@ import com.vitorpamplona.amethyst.ui.theme.MarkdownTextStyle
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.amethyst.ui.theme.markdownStyle
|
||||
import com.vitorpamplona.amethyst.ui.uriToRoute
|
||||
import com.vitorpamplona.quartz.nip01Core.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip92IMeta.imetasByUrl
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -93,7 +94,7 @@ fun RenderContentAsMarkdown(
|
||||
remember(content) {
|
||||
MarkdownMediaRenderer(
|
||||
startOfText = content.take(100),
|
||||
tags = tags,
|
||||
imetaByUrl = tags?.lists?.imetasByUrl() ?: emptyMap(),
|
||||
canPreview = canPreview,
|
||||
quotesLeft = quotesLeft,
|
||||
backgroundColor = backgroundColor,
|
||||
|
||||
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.dal
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
|
||||
class ChatroomFeedFilter(
|
||||
val withUser: ChatroomKey,
|
||||
|
||||
+3
-3
@@ -25,9 +25,9 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.actions.relays.updated
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEventIds
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatroomKeyable
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
|
||||
class ChatroomListKnownFeedFilter(
|
||||
val account: Account,
|
||||
|
||||
+3
-3
@@ -23,9 +23,9 @@ package com.vitorpamplona.amethyst.ui.dal
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.actions.relays.updated
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.ChatroomKeyable
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
||||
|
||||
class ChatroomListNewFeedFilter(
|
||||
val account: Account,
|
||||
|
||||
@@ -24,9 +24,9 @@ import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.isTaggedAddressableNote
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
|
||||
class CommunityFeedFilter(
|
||||
val note: AddressableNote,
|
||||
|
||||
@@ -24,8 +24,8 @@ import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.IsInPublicChatChannel
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.base.IsInPublicChatChannel
|
||||
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
|
||||
@@ -85,7 +85,7 @@ open class DiscoverChatFeedFilter(
|
||||
null
|
||||
}
|
||||
} else if (noteEvent is IsInPublicChatChannel) {
|
||||
val channel = noteEvent.channel()?.let { LocalCache.checkGetOrCreateNote(it) }
|
||||
val channel = noteEvent.channelId()?.let { LocalCache.checkGetOrCreateNote(it) }
|
||||
val channelEvent = channel?.event
|
||||
|
||||
if (channel != null &&
|
||||
|
||||
+6
-7
@@ -23,12 +23,11 @@ package com.vitorpamplona.amethyst.ui.dal
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip19Bech32.parseAtagUnckecked
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
|
||||
open class DiscoverCommunityFeedFilter(
|
||||
val account: Account,
|
||||
@@ -54,7 +53,7 @@ open class DiscoverCommunityFeedFilter(
|
||||
val notes =
|
||||
LocalCache.addressables.mapNotNullIntoSet { key, note ->
|
||||
val noteEvent = note.event
|
||||
if (noteEvent == null && shouldInclude(ATag.parseAtagUnckecked(key), filterParams)) {
|
||||
if (noteEvent == null && shouldInclude(Address.parse(key), filterParams)) {
|
||||
// send unloaded communities to the screen
|
||||
note
|
||||
} else if (noteEvent is CommunityDefinitionEvent && filterParams.match(noteEvent)) {
|
||||
@@ -86,7 +85,7 @@ open class DiscoverCommunityFeedFilter(
|
||||
if (noteEvent is CommunityDefinitionEvent && filterParams.match(noteEvent)) {
|
||||
listOf(note)
|
||||
} else if (noteEvent is CommunityPostApprovalEvent) {
|
||||
noteEvent.communities().mapNotNull {
|
||||
noteEvent.communityAddresses().mapNotNull {
|
||||
val definitionNote = LocalCache.getOrCreateAddressableNote(it)
|
||||
val definitionEvent = definitionNote.event
|
||||
|
||||
@@ -106,7 +105,7 @@ open class DiscoverCommunityFeedFilter(
|
||||
}
|
||||
|
||||
private fun shouldInclude(
|
||||
aTag: ATag?,
|
||||
aTag: Address?,
|
||||
params: FilterByListParams,
|
||||
) = aTag != null && aTag.kind == CommunityDefinitionEvent.KIND && params.match(aTag)
|
||||
|
||||
|
||||
@@ -26,10 +26,8 @@ import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
|
||||
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent.Companion.STATUS_ENDED
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent.Companion.STATUS_LIVE
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent.Companion.STATUS_PLANNED
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.StatusTag
|
||||
|
||||
open class DiscoverLiveFeedFilter(
|
||||
val account: Account,
|
||||
@@ -93,9 +91,9 @@ open class DiscoverLiveFeedFilter(
|
||||
|
||||
fun convertStatusToOrder(status: String?): Int =
|
||||
when (status) {
|
||||
STATUS_LIVE -> 2
|
||||
STATUS_PLANNED -> 1
|
||||
STATUS_ENDED -> 0
|
||||
StatusTag.STATUS.LIVE.code -> 2
|
||||
StatusTag.STATUS.PLANNED.code -> 1
|
||||
StatusTag.STATUS.ENDED.code -> 0
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
open class DiscoverNIP89FeedFilter(
|
||||
@@ -76,7 +76,7 @@ open class DiscoverNIP89FeedFilter(
|
||||
val filterParams = buildFilterParams(account)
|
||||
return noteEvent.appMetaData()?.subscription != true &&
|
||||
filterParams.match(noteEvent) &&
|
||||
noteEvent.includeKind("5300") &&
|
||||
noteEvent.includeKind(5300) &&
|
||||
noteEvent.createdAt > TimeUtils.now() - lastAnnounced // && params.match(noteEvent)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,14 +24,14 @@ import com.vitorpamplona.amethyst.model.AROUND_ME
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.isTaggedAddressableNotes
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.isTaggedGeoHashes
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.isTaggedHashes
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
class FilterByListParams(
|
||||
@@ -68,10 +68,10 @@ class FilterByListParams(
|
||||
}
|
||||
}
|
||||
|
||||
fun isATagInList(aTag: ATag): Boolean {
|
||||
fun isAuthorInFollows(address: Address): Boolean {
|
||||
if (followLists == null) return false
|
||||
|
||||
return aTag.pubKeyHex in followLists.authors
|
||||
return address.pubKeyHex in followLists.authors
|
||||
}
|
||||
|
||||
fun match(
|
||||
@@ -81,10 +81,10 @@ class FilterByListParams(
|
||||
(isHiddenList || isNotHidden(noteEvent.pubKey)) &&
|
||||
isNotInTheFuture(noteEvent)
|
||||
|
||||
fun match(aTag: ATag?) =
|
||||
aTag != null &&
|
||||
(isGlobal || isATagInList(aTag)) &&
|
||||
(isHiddenList || isNotHidden(aTag.pubKeyHex))
|
||||
fun match(address: Address?) =
|
||||
address != null &&
|
||||
(isGlobal || isAuthorInFollows(address)) &&
|
||||
(isHiddenList || isNotHidden(address.pubKeyHex))
|
||||
|
||||
companion object {
|
||||
fun showHiddenKey(
|
||||
|
||||
@@ -23,13 +23,13 @@ package com.vitorpamplona.amethyst.ui.dal
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.isTaggedGeoHash
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
|
||||
|
||||
class GeoHashFeedFilter(
|
||||
|
||||
@@ -23,15 +23,15 @@ package com.vitorpamplona.amethyst.ui.dal
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.isTaggedHash
|
||||
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
|
||||
|
||||
class HashtagFeedFilter(
|
||||
|
||||
+2
-2
@@ -27,10 +27,10 @@ import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
|
||||
class HomeConversationsFeedFilter(
|
||||
val account: Account,
|
||||
|
||||
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.ui.dal
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
|
||||
@@ -23,23 +23,25 @@ package com.vitorpamplona.amethyst.ui.dal
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.quartz.nip01Core.HexKey
|
||||
import com.vitorpamplona.quartz.experimental.forks.forkFromVersion
|
||||
import com.vitorpamplona.quartz.experimental.forks.isForkFromAddressWithPubkey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseTextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitIssueEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.GitPatchEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent
|
||||
import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip58Badges.BadgeProfilesEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryRequestEvent
|
||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
||||
@@ -138,13 +140,13 @@ class NotificationFeedFilter(
|
||||
return true
|
||||
}
|
||||
|
||||
if (event is BaseTextNoteEvent) {
|
||||
if (event is BaseThreadedEvent) {
|
||||
if (note.replyTo?.any { it.author?.pubkeyHex == authorHex } == true) return true
|
||||
|
||||
val isAuthoredPostCited = event.findCitations().any { LocalCache.getNoteIfExists(it)?.author?.pubkeyHex == authorHex }
|
||||
val isAuthorDirectlyCited = event.citedUsers().contains(authorHex)
|
||||
val isAuthorOfAFork =
|
||||
event.isForkFromAddressWithPubkey(authorHex) || (event.forkFromVersion()?.let { LocalCache.getNoteIfExists(it)?.author?.pubkeyHex == authorHex } == true)
|
||||
event.isForkFromAddressWithPubkey(authorHex) || (event.forkFromVersion()?.let { LocalCache.getNoteIfExists(it.eventId)?.author?.pubkeyHex == authorHex } == true)
|
||||
|
||||
return isAuthoredPostCited || isAuthorDirectlyCited || isAuthorOfAFork
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user