Merge branch 'vitorpamplona:main' into labeled-bookmarks
This commit is contained in:
@@ -121,6 +121,7 @@ import com.vitorpamplona.quartz.experimental.profileGallery.dimension
|
|||||||
import com.vitorpamplona.quartz.experimental.profileGallery.fromEvent
|
import com.vitorpamplona.quartz.experimental.profileGallery.fromEvent
|
||||||
import com.vitorpamplona.quartz.experimental.profileGallery.hash
|
import com.vitorpamplona.quartz.experimental.profileGallery.hash
|
||||||
import com.vitorpamplona.quartz.experimental.profileGallery.mimeType
|
import com.vitorpamplona.quartz.experimental.profileGallery.mimeType
|
||||||
|
import com.vitorpamplona.quartz.experimental.relationshipStatus.ContactCardEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
@@ -209,12 +210,17 @@ import com.vitorpamplona.quartz.utils.containsAny
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.flow.emitAll
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.transformLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -1721,6 +1727,25 @@ class Account(
|
|||||||
).toSet()
|
).toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
fun loadUserCardFlow(target: HexKey): Flow<Int?> =
|
||||||
|
trustProviderList.liveUserRankProvider
|
||||||
|
.transformLatest { provider ->
|
||||||
|
if (provider != null) {
|
||||||
|
emitAll(
|
||||||
|
cache
|
||||||
|
.getOrCreateAddressableNote(
|
||||||
|
ContactCardEvent.createAddress(provider.pubkey, target),
|
||||||
|
).flow()
|
||||||
|
.metadata.stateFlow,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
emit(null)
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
|
(it?.note?.event as? ContactCardEvent)?.rank()
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun saveDMRelayList(dmRelays: List<NormalizedRelayUrl>) = sendLiterallyEverywhere(dmRelayList.saveRelayList(dmRelays))
|
suspend fun saveDMRelayList(dmRelays: List<NormalizedRelayUrl>) = sendLiterallyEverywhere(dmRelayList.saveRelayList(dmRelays))
|
||||||
|
|
||||||
suspend fun savePrivateOutboxRelayList(relays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(privateStorageRelayList.saveRelayList(relays))
|
suspend fun savePrivateOutboxRelayList(relays: List<NormalizedRelayUrl>) = sendMyPublicAndPrivateOutbox(privateStorageRelayList.saveRelayList(relays))
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ import com.vitorpamplona.quartz.nip01Core.core.toImmutableListOfLists
|
|||||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
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.PTag
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||||
@@ -68,6 +66,8 @@ class User(
|
|||||||
var relaysBeingUsed = mapOf<NormalizedRelayUrl, RelayInfo>()
|
var relaysBeingUsed = mapOf<NormalizedRelayUrl, RelayInfo>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var flowSet: UserFlowSet? = null
|
||||||
|
|
||||||
fun pubkey() = Hex.decode(pubkeyHex)
|
fun pubkey() = Hex.decode(pubkeyHex)
|
||||||
|
|
||||||
fun pubkeyNpub() = pubkey().toNpub()
|
fun pubkeyNpub() = pubkey().toNpub()
|
||||||
@@ -251,10 +251,6 @@ class User(
|
|||||||
|
|
||||||
fun isFollowing(user: User): Boolean = latestContactList?.isTaggedUser(user.pubkeyHex) ?: false
|
fun isFollowing(user: User): Boolean = latestContactList?.isTaggedUser(user.pubkeyHex) ?: false
|
||||||
|
|
||||||
fun isFollowingHashtag(tag: String) = latestContactList?.isTaggedHash(tag) ?: false
|
|
||||||
|
|
||||||
fun isFollowingGeohash(geoTag: String) = latestContactList?.isTaggedGeoHash(geoTag) ?: false
|
|
||||||
|
|
||||||
fun transientFollowCount(): Int? = latestContactList?.unverifiedFollowKeySet()?.size
|
fun transientFollowCount(): Int? = latestContactList?.unverifiedFollowKeySet()?.size
|
||||||
|
|
||||||
suspend fun transientFollowerCount(): Int = LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
|
suspend fun transientFollowerCount(): Int = LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
|
||||||
@@ -303,8 +299,6 @@ class User(
|
|||||||
|
|
||||||
fun anyNameStartsWith(username: String): Boolean = info?.anyNameStartsWith(username) ?: false
|
fun anyNameStartsWith(username: String): Boolean = info?.anyNameStartsWith(username) ?: false
|
||||||
|
|
||||||
var flowSet: UserFlowSet? = null
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun createOrDestroyFlowSync(create: Boolean) {
|
fun createOrDestroyFlowSync(create: Boolean) {
|
||||||
if (create) {
|
if (create) {
|
||||||
|
|||||||
+36
@@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
|||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.NoteState
|
import com.vitorpamplona.amethyst.model.NoteState
|
||||||
import com.vitorpamplona.quartz.experimental.trustedAssertions.list.TrustProviderListEvent
|
import com.vitorpamplona.quartz.experimental.trustedAssertions.list.TrustProviderListEvent
|
||||||
|
import com.vitorpamplona.quartz.experimental.trustedAssertions.list.tags.ProviderTypes
|
||||||
import com.vitorpamplona.quartz.experimental.trustedAssertions.list.tags.ServiceProviderTag
|
import com.vitorpamplona.quartz.experimental.trustedAssertions.list.tags.ServiceProviderTag
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
@@ -36,6 +37,7 @@ import kotlinx.coroutines.GlobalScope
|
|||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.transformLatest
|
import kotlinx.coroutines.flow.transformLatest
|
||||||
@@ -76,6 +78,40 @@ class TrustProviderListState(
|
|||||||
emptySet(),
|
emptySet(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
val liveUserRankProvider: StateFlow<ServiceProviderTag?> =
|
||||||
|
liveTrustProviderList
|
||||||
|
.map {
|
||||||
|
it.firstOrNull { it.service == ProviderTypes.rank }
|
||||||
|
}.onStart {
|
||||||
|
emit(
|
||||||
|
liveTrustProviderList.value.firstOrNull {
|
||||||
|
it.service == ProviderTypes.rank
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
.stateIn(
|
||||||
|
scope,
|
||||||
|
SharingStarted.Eagerly,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
val liveUserFollowerCount: StateFlow<ServiceProviderTag?> =
|
||||||
|
liveTrustProviderList
|
||||||
|
.map { tagList ->
|
||||||
|
tagList.firstOrNull { it.service == ProviderTypes.followerCount }
|
||||||
|
}.onStart {
|
||||||
|
emit(
|
||||||
|
liveTrustProviderList.value.firstOrNull { it.service == ProviderTypes.followerCount },
|
||||||
|
)
|
||||||
|
}.flowOn(Dispatchers.IO)
|
||||||
|
.stateIn(
|
||||||
|
scope,
|
||||||
|
SharingStarted.Eagerly,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
settings.backupTrustProviderList?.let { event ->
|
settings.backupTrustProviderList?.let { event ->
|
||||||
Log.d("AccountRegisterObservers", "Loading saved ephemeral chat list")
|
Log.d("AccountRegisterObservers", "Loading saved ephemeral chat list")
|
||||||
|
|||||||
+2
@@ -25,6 +25,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.loaders.UserOutboxFinderSubAssembler
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.loaders.UserOutboxFinderSubAssembler
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers.UserCardsSubAssembler
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers.UserReportsSubAssembler
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers.UserReportsSubAssembler
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers.UserWatcherSubAssembler
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers.UserWatcherSubAssembler
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||||
@@ -46,6 +47,7 @@ class UserFinderFilterAssembler(
|
|||||||
UserOutboxFinderSubAssembler(client, cache, failureTracker, ::allKeys),
|
UserOutboxFinderSubAssembler(client, cache, failureTracker, ::allKeys),
|
||||||
UserWatcherSubAssembler(client, cache, ::allKeys),
|
UserWatcherSubAssembler(client, cache, ::allKeys),
|
||||||
UserReportsSubAssembler(client, ::allKeys),
|
UserReportsSubAssembler(client, ::allKeys),
|
||||||
|
UserCardsSubAssembler(client, ::allKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
override fun invalidateFilters() = group.forEach { it.invalidateFilters() }
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
|
import com.vitorpamplona.quartz.experimental.relationshipStatus.ContactCardEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
|
||||||
|
val ContactCardKindList = listOf(ContactCardEvent.KIND)
|
||||||
|
|
||||||
|
fun filterContactCardsToKeysFromTrusted(
|
||||||
|
targets: Set<HexKey>,
|
||||||
|
trustedAccounts: Map<NormalizedRelayUrl, Set<HexKey>>,
|
||||||
|
since: SincePerRelayMap?,
|
||||||
|
): List<RelayBasedFilter> {
|
||||||
|
if (targets.isEmpty() || trustedAccounts.isEmpty()) return emptyList()
|
||||||
|
val sortedTargets = mapOf("d" to targets.sorted())
|
||||||
|
return trustedAccounts.mapNotNull { relayAuthors ->
|
||||||
|
if (relayAuthors.value.isNotEmpty()) {
|
||||||
|
RelayBasedFilter(
|
||||||
|
relay = relayAuthors.key,
|
||||||
|
filter =
|
||||||
|
Filter(
|
||||||
|
kinds = ContactCardKindList,
|
||||||
|
authors = relayAuthors.value.sorted(),
|
||||||
|
tags = sortedTargets,
|
||||||
|
since = since?.get(relayAuthors.key)?.time,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-2
@@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers
|
|||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
||||||
import com.vitorpamplona.quartz.experimental.relationshipStatus.ContactCardEvent
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
@@ -38,7 +37,6 @@ val UserMetadataForKeyKinds =
|
|||||||
listOf(
|
listOf(
|
||||||
MetadataEvent.KIND,
|
MetadataEvent.KIND,
|
||||||
StatusEvent.KIND,
|
StatusEvent.KIND,
|
||||||
ContactCardEvent.KIND,
|
|
||||||
AdvertisedRelayListEvent.KIND,
|
AdvertisedRelayListEvent.KIND,
|
||||||
ChatMessageRelayListEvent.KIND,
|
ChatMessageRelayListEvent.KIND,
|
||||||
)
|
)
|
||||||
|
|||||||
+147
@@ -0,0 +1,147 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.watchers
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubEoseManager
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.UserFinderQueryState
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.EOSEAccountFast
|
||||||
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
|
import com.vitorpamplona.ammolite.relays.filters.MutableTime
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.utils.mapOfSet
|
||||||
|
|
||||||
|
class UserCardsSubAssembler(
|
||||||
|
client: INostrClient,
|
||||||
|
allKeys: () -> Set<UserFinderQueryState>,
|
||||||
|
) : SingleSubEoseManager<UserFinderQueryState>(client, allKeys) {
|
||||||
|
var lastUsersOnFilter: Set<User> = emptySet()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This assembler saves the EOSE per user key. That EOSE includes their metadata, etc
|
||||||
|
* and reports, but only from trusted accounts (follows of all logged in users).
|
||||||
|
*/
|
||||||
|
var latestEOSEs: EOSEAccountFast<User> = EOSEAccountFast<User>(2000)
|
||||||
|
|
||||||
|
override fun newEose(
|
||||||
|
relay: NormalizedRelayUrl,
|
||||||
|
time: Long,
|
||||||
|
filters: List<Filter>?,
|
||||||
|
) {
|
||||||
|
lastUsersOnFilter.forEach {
|
||||||
|
latestEOSEs.newEose(it, relay, time)
|
||||||
|
}
|
||||||
|
super.newEose(relay, time, filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateFilter(
|
||||||
|
keys: List<UserFinderQueryState>,
|
||||||
|
since: SincePerRelayMap?,
|
||||||
|
): List<RelayBasedFilter>? {
|
||||||
|
if (keys.isEmpty()) return null
|
||||||
|
|
||||||
|
lastUsersOnFilter = keys.mapTo(mutableSetOf()) { it.user }
|
||||||
|
|
||||||
|
if (lastUsersOnFilter.isEmpty()) return null
|
||||||
|
|
||||||
|
val accounts = keys.mapTo(mutableSetOf()) { it.account }
|
||||||
|
|
||||||
|
val trustedAccounts: Map<NormalizedRelayUrl, Set<HexKey>> =
|
||||||
|
mapOfSet {
|
||||||
|
accounts.forEach { account ->
|
||||||
|
account.outboxRelays.flow.value.map {
|
||||||
|
add(it, account.userProfile().pubkeyHex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
accounts.map { it.trustProviderList.liveUserRankProvider.value }.forEach { account ->
|
||||||
|
if (account != null) {
|
||||||
|
add(account.relayUrl, account.pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupByRelayPresence(lastUsersOnFilter, latestEOSEs, trustedAccounts.keys)
|
||||||
|
.map { group ->
|
||||||
|
val groupIds = group.map { it.pubkeyHex }.toSet()
|
||||||
|
if (groupIds.isNotEmpty()) {
|
||||||
|
val minEOSEs = findMinimumEOSEsForUsers(group, latestEOSEs)
|
||||||
|
filterContactCardsToKeysFromTrusted(groupIds, trustedAccounts, minEOSEs)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun groupByRelayPresence(
|
||||||
|
users: Iterable<User>,
|
||||||
|
eoseCache: EOSEAccountFast<User>,
|
||||||
|
inRelays: Set<NormalizedRelayUrl>,
|
||||||
|
): Collection<List<User>> {
|
||||||
|
if (users.none()) return emptyList()
|
||||||
|
|
||||||
|
val relaySnapshot = inRelays.toSet()
|
||||||
|
|
||||||
|
return users
|
||||||
|
.groupBy { user ->
|
||||||
|
val relaysForUser = eoseCache.sinceRelaySet(user)
|
||||||
|
if (relaysForUser.isNullOrEmpty() || relaySnapshot.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
val intersection = relaysForUser.filter { it in relaySnapshot }.sorted()
|
||||||
|
if (intersection.isEmpty()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
intersection.hashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.values
|
||||||
|
.map {
|
||||||
|
// important to keep in order otherwise the Relay thinks the filter has changed and we REQ again
|
||||||
|
it.sortedBy { it.pubkeyHex }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findMinimumEOSEsForUsers(
|
||||||
|
users: List<User>,
|
||||||
|
eoseCache: EOSEAccountFast<User>,
|
||||||
|
): SincePerRelayMap {
|
||||||
|
val minLatestEOSEs = mutableMapOf<NormalizedRelayUrl, MutableTime>()
|
||||||
|
|
||||||
|
users.forEach {
|
||||||
|
eoseCache.since(it)?.forEach {
|
||||||
|
val minEose = minLatestEOSEs[it.key]
|
||||||
|
if (minEose == null) {
|
||||||
|
minLatestEOSEs.put(it.key, it.value.copy())
|
||||||
|
} else {
|
||||||
|
minEose.updateIfOlder(it.value.time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return minLatestEOSEs
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun distinct(key: UserFinderQueryState) = key.user
|
||||||
|
}
|
||||||
@@ -21,19 +21,27 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
@@ -45,7 +53,12 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
|||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Font10SP
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Font6SP
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.SmallBorder
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||||
|
|
||||||
@@ -354,6 +367,12 @@ fun BaseUserPicture(
|
|||||||
FollowingIcon(Modifier.size(size.div(3.5f)))
|
FollowingIcon(Modifier.size(size.div(3.5f)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WatchUserCards(baseUser.pubkeyHex, accountViewModel) { score ->
|
||||||
|
if (score != null) {
|
||||||
|
ScoreTag(score, size, Modifier.align(Alignment.BottomCenter))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,9 +401,109 @@ fun BaseUserPicture(
|
|||||||
FollowingIcon(Modifier.size(size.div(3.5f)))
|
FollowingIcon(Modifier.size(size.div(3.5f)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WatchUserCards(baseUserHex, accountViewModel) { score ->
|
||||||
|
if (score != null) {
|
||||||
|
ScoreTag(score, size, Modifier.align(Alignment.BottomCenter))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun ScoreTag55Preview() {
|
||||||
|
val accountViewModel = mockAccountViewModel()
|
||||||
|
ThemeComparisonColumn {
|
||||||
|
Row {
|
||||||
|
var size = 75.dp
|
||||||
|
Box(Modifier.size(size), contentAlignment = Alignment.TopEnd) {
|
||||||
|
InnerUserPicture(
|
||||||
|
userHex = "AABBCC",
|
||||||
|
userPicture = "http://null",
|
||||||
|
userName = "vitor",
|
||||||
|
size = size,
|
||||||
|
modifier = Modifier,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
FollowingIcon(Modifier.size(size.div(3.5f)))
|
||||||
|
ScoreTag(100, size, Modifier.align(Alignment.BottomCenter))
|
||||||
|
}
|
||||||
|
size = 55.dp
|
||||||
|
Box(Modifier.size(size), contentAlignment = Alignment.TopEnd) {
|
||||||
|
InnerUserPicture(
|
||||||
|
userHex = "AABBCC",
|
||||||
|
userPicture = "http://null",
|
||||||
|
userName = "vitor",
|
||||||
|
size = size,
|
||||||
|
modifier = Modifier,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
FollowingIcon(Modifier.size(size.div(3.5f)))
|
||||||
|
ScoreTag(100, size, Modifier.align(Alignment.BottomCenter))
|
||||||
|
}
|
||||||
|
size = 35.dp
|
||||||
|
Box(Modifier.size(size), contentAlignment = Alignment.TopEnd) {
|
||||||
|
InnerUserPicture(
|
||||||
|
userHex = "AABBCC",
|
||||||
|
userPicture = "http://null",
|
||||||
|
userName = "vitor",
|
||||||
|
size = size,
|
||||||
|
modifier = Modifier,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
FollowingIcon(Modifier.size(size.div(3.5f)))
|
||||||
|
ScoreTag(100, size, Modifier.align(Alignment.BottomCenter))
|
||||||
|
}
|
||||||
|
size = 25.dp
|
||||||
|
Box(Modifier.size(size), contentAlignment = Alignment.TopEnd) {
|
||||||
|
InnerUserPicture(
|
||||||
|
userHex = "AABBCC",
|
||||||
|
userPicture = "http://null",
|
||||||
|
userName = "vitor",
|
||||||
|
size = size,
|
||||||
|
modifier = Modifier,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
FollowingIcon(Modifier.size(size.div(3.5f)))
|
||||||
|
ScoreTag(100, size, Modifier.align(Alignment.BottomCenter))
|
||||||
|
}
|
||||||
|
size = 18.dp
|
||||||
|
Box(Modifier.size(size), contentAlignment = Alignment.TopEnd) {
|
||||||
|
InnerUserPicture(
|
||||||
|
userHex = "AABBCC",
|
||||||
|
userPicture = "http://null",
|
||||||
|
userName = "vitor",
|
||||||
|
size = size,
|
||||||
|
modifier = Modifier,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
FollowingIcon(Modifier.size(size.div(3.5f)))
|
||||||
|
ScoreTag(100, size, Modifier.align(Alignment.BottomCenter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ScoreTag(
|
||||||
|
score: Int,
|
||||||
|
size: Dp,
|
||||||
|
modifier: Modifier,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = score.toString(),
|
||||||
|
color = Color.White,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = if (size > 34.dp) Font10SP else Font6SP,
|
||||||
|
modifier =
|
||||||
|
modifier
|
||||||
|
.clip(SmallBorder)
|
||||||
|
.background(Color.Black)
|
||||||
|
.padding(horizontal = if (size > 34.dp) 4.dp else 3.dp, vertical = 0.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoadUserProfilePicture(
|
fun LoadUserProfilePicture(
|
||||||
baseUser: User,
|
baseUser: User,
|
||||||
@@ -458,3 +577,19 @@ fun WatchUserFollows(
|
|||||||
onFollowChanges(state.authors.contains(userHex))
|
onFollowChanges(state.authors.contains(userHex))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WatchUserCards(
|
||||||
|
userHex: String,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
onScoreChanges: @Composable (Int?) -> Unit,
|
||||||
|
) {
|
||||||
|
val flow =
|
||||||
|
remember(userHex) {
|
||||||
|
accountViewModel.account.loadUserCardFlow(userHex)
|
||||||
|
}
|
||||||
|
|
||||||
|
val score by flow.collectAsStateWithLifecycle(null)
|
||||||
|
|
||||||
|
onScoreChanges(score)
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ val Typography =
|
|||||||
*/
|
*/
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val Font4SP = 4.sp
|
||||||
|
val Font6SP = 6.sp
|
||||||
|
val Font8SP = 8.sp
|
||||||
val Font10SP = 10.sp
|
val Font10SP = 10.sp
|
||||||
val Font12SP = 12.sp
|
val Font12SP = 12.sp
|
||||||
val Font14SP = 14.sp
|
val Font14SP = 14.sp
|
||||||
|
|||||||
+13
-1
@@ -24,10 +24,12 @@ import androidx.compose.runtime.Immutable
|
|||||||
import com.vitorpamplona.quartz.experimental.relationshipStatus.tags.PetNameTag
|
import com.vitorpamplona.quartz.experimental.relationshipStatus.tags.PetNameTag
|
||||||
import com.vitorpamplona.quartz.experimental.relationshipStatus.tags.RankTag
|
import com.vitorpamplona.quartz.experimental.relationshipStatus.tags.RankTag
|
||||||
import com.vitorpamplona.quartz.experimental.relationshipStatus.tags.SummaryTag
|
import com.vitorpamplona.quartz.experimental.relationshipStatus.tags.SummaryTag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.tagArray
|
import com.vitorpamplona.quartz.nip01Core.core.tagArray
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
|
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
|
||||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||||
import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEvent
|
import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEvent
|
||||||
@@ -49,10 +51,20 @@ class ContactCardEvent(
|
|||||||
|
|
||||||
fun summary() = tags.firstNotNullOfOrNull(SummaryTag::parse)
|
fun summary() = tags.firstNotNullOfOrNull(SummaryTag::parse)
|
||||||
|
|
||||||
companion object Companion {
|
companion object {
|
||||||
const val KIND = 30382
|
const val KIND = 30382
|
||||||
const val ALT = "Contact Card"
|
const val ALT = "Contact Card"
|
||||||
|
|
||||||
|
fun createAddress(
|
||||||
|
owner: HexKey,
|
||||||
|
target: HexKey,
|
||||||
|
): Address = Address(KIND, owner, target)
|
||||||
|
|
||||||
|
fun createAddressTag(
|
||||||
|
owner: HexKey,
|
||||||
|
target: HexKey,
|
||||||
|
): ATag = ATag(KIND, owner, target, null)
|
||||||
|
|
||||||
suspend fun create(
|
suspend fun create(
|
||||||
targetUser: HexKey,
|
targetUser: HexKey,
|
||||||
petName: String? = null,
|
petName: String? = null,
|
||||||
|
|||||||
-25
@@ -61,28 +61,3 @@ data class ServiceProviderTag(
|
|||||||
fun assemble(id: ServiceProviderTag) = arrayOf(id.service, id.pubkey, id.relayUrl)
|
fun assemble(id: ServiceProviderTag) = arrayOf(id.service, id.pubkey, id.relayUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ServiceType(
|
|
||||||
val kind: Int,
|
|
||||||
val type: String,
|
|
||||||
) {
|
|
||||||
fun toValue() = assemble(kind, type)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun assemble(
|
|
||||||
kind: Int,
|
|
||||||
type: String,
|
|
||||||
) = "$kind:$type"
|
|
||||||
|
|
||||||
fun parse(serviceType: String): ServiceType? {
|
|
||||||
val (kindStr, type) = serviceType.split(":", limit = 2)
|
|
||||||
val kind = kindStr.toIntOrNull() ?: return null
|
|
||||||
return ServiceType(kind, type)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isOfKind(
|
|
||||||
serviceType: String,
|
|
||||||
kind: String,
|
|
||||||
) = serviceType.startsWith(kind) && serviceType[kind.length] == ':'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.experimental.trustedAssertions.list.tags
|
||||||
|
|
||||||
|
data class ServiceType(
|
||||||
|
val kind: Int,
|
||||||
|
val type: String,
|
||||||
|
) {
|
||||||
|
fun toValue() = assemble(kind, type)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun assemble(
|
||||||
|
kind: Int,
|
||||||
|
type: String,
|
||||||
|
) = "$kind:$type"
|
||||||
|
|
||||||
|
fun parse(serviceType: String): ServiceType? {
|
||||||
|
val (kindStr, type) = serviceType.split(":", limit = 2)
|
||||||
|
val kind = kindStr.toIntOrNull() ?: return null
|
||||||
|
return ServiceType(kind, type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isOfKind(
|
||||||
|
serviceType: String,
|
||||||
|
kind: String,
|
||||||
|
) = serviceType.startsWith(kind) && serviceType[kind.length] == ':'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object ProviderTypes {
|
||||||
|
val rank = ServiceType(30382, "rank")
|
||||||
|
val followerCount = ServiceType(30382, "followers")
|
||||||
|
|
||||||
|
val firstPost = ServiceType(30382, "first_created_at")
|
||||||
|
val postCount = ServiceType(30382, "post_cnt")
|
||||||
|
val replyCount = ServiceType(30382, "reply_cnt")
|
||||||
|
val reactionsCount = ServiceType(30382, "reactions_cnt")
|
||||||
|
val zapReceivedAmount = ServiceType(30382, "zap_amt_recd")
|
||||||
|
val zapSentAmount = ServiceType(30382, "zap_amt_sent")
|
||||||
|
val zapReceivedCount = ServiceType(30382, "zap_cnt_recd")
|
||||||
|
val zapSentCount = ServiceType(30382, "zap_cnt_sent")
|
||||||
|
val averageZapAmountReceivedPerDay = ServiceType(30382, "zap_avg_amt_day_recd")
|
||||||
|
val averageZapAmountSentPerDay = ServiceType(30382, "zap_avg_amt_day_sent")
|
||||||
|
val reportsReceivedCount = ServiceType(30382, "reports_cnt_recd")
|
||||||
|
val reportsSentCount = ServiceType(30382, "reports_cnt_sent")
|
||||||
|
val topics = ServiceType(30382, "t")
|
||||||
|
val activeFrom = ServiceType(30382, "active_hours_start")
|
||||||
|
val activeTo = ServiceType(30382, "active_hours_end")
|
||||||
|
}
|
||||||
+1
-1
@@ -22,7 +22,7 @@ package com.vitorpamplona.quartz.utils.sha256
|
|||||||
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
val pool = Sha256Pool(5) // max parallel operations
|
val pool = Sha256Pool(10) // max parallel operations
|
||||||
|
|
||||||
actual fun sha256(data: ByteArray) = pool.hash(data)
|
actual fun sha256(data: ByteArray) = pool.hash(data)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user