Merge branch 'main' into main

This commit is contained in:
greenart7c3
2024-03-18 07:07:32 -03:00
committed by GitHub
127 changed files with 1448 additions and 1034 deletions
@@ -28,8 +28,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.HashTagSegment import com.vitorpamplona.amethyst.commons.richtext.HashTagSegment
import com.vitorpamplona.amethyst.commons.RegularTextSegment import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment
import com.vitorpamplona.amethyst.ui.components.HashTag import com.vitorpamplona.amethyst.ui.components.HashTag
import com.vitorpamplona.amethyst.ui.components.RenderRegular import com.vitorpamplona.amethyst.ui.components.RenderRegular
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
@@ -24,6 +24,7 @@ import android.util.Log
import android.util.LruCache import android.util.LruCache
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.commons.data.LargeCache
import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.components.BundledInsert import com.vitorpamplona.amethyst.ui.components.BundledInsert
@@ -121,30 +122,16 @@ import java.time.Instant
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import kotlin.time.measureTimedValue
object LocalCache { object LocalCache {
val antiSpam = AntiSpamFilter() val antiSpam = AntiSpamFilter()
private val users = ConcurrentHashMap<HexKey, User>(5000) val users = LargeCache<HexKey, User>()
private val notes = ConcurrentHashMap<HexKey, Note>(5000) val notes = LargeCache<HexKey, Note>()
val addressables = LargeCache<String, AddressableNote>()
val channels = ConcurrentHashMap<HexKey, Channel>() val channels = ConcurrentHashMap<HexKey, Channel>()
val addressables = ConcurrentHashMap<String, AddressableNote>(100) val awaitingPaymentRequests = ConcurrentHashMap<HexKey, Pair<Note?, (LnZapPaymentResponseEvent) -> Unit>>(10)
val awaitingPaymentRequests =
ConcurrentHashMap<HexKey, Pair<Note?, (LnZapPaymentResponseEvent) -> Unit>>(10)
var noteListCache: List<Note> = emptyList()
var userListCache: List<User> = emptyList()
fun updateListCache() {
val (value, elapsed) =
measureTimedValue {
noteListCache = ArrayList(notes.values)
userListCache = ArrayList(users.values)
}
Log.d("LocalCache", "UpdateListCache $elapsed")
}
fun checkGetOrCreateUser(key: String): User? { fun checkGetOrCreateUser(key: String): User? {
// checkNotInMainThread() // checkNotInMainThread()
@@ -166,27 +153,24 @@ object LocalCache {
fun getOrCreateUser(key: HexKey): User { fun getOrCreateUser(key: HexKey): User {
// checkNotInMainThread() // checkNotInMainThread()
require(isValidHex(key = key)) { "$key is not a valid hex" }
return users[key] return users.getOrCreate(key) {
?: run { User(it)
require(isValidHex(key = key)) { "$key is not a valid hex" } }
val newObject = User(key)
users.putIfAbsent(key, newObject) ?: newObject
}
} }
fun getUserIfExists(key: String): User? { fun getUserIfExists(key: String): User? {
if (key.isEmpty()) return null if (key.isEmpty()) return null
return users[key] return users.get(key)
} }
fun getAddressableNoteIfExists(key: String): AddressableNote? { fun getAddressableNoteIfExists(key: String): AddressableNote? {
return addressables[key] return addressables.get(key)
} }
fun getNoteIfExists(key: String): Note? { fun getNoteIfExists(key: String): Note? {
return addressables[key] ?: notes[key] return addressables.get(key) ?: notes.get(key)
} }
fun getChannelIfExists(key: String): Channel? { fun getChannelIfExists(key: String): Channel? {
@@ -226,24 +210,21 @@ object LocalCache {
): Note { ): Note {
checkNotInMainThread() checkNotInMainThread()
return notes.get(idHex) require(isValidHex(idHex)) { "$idHex is not a valid hex" }
?: run {
require(isValidHex(idHex)) { "$idHex is not a valid hex" }
notes.putIfAbsent(idHex, note) ?: note return notes.getOrCreate(idHex) {
} note
}
} }
fun getOrCreateNote(idHex: String): Note { fun getOrCreateNote(idHex: String): Note {
checkNotInMainThread() checkNotInMainThread()
return notes.get(idHex) require(isValidHex(idHex)) { "$idHex is not a valid hex" }
?: run {
require(isValidHex(idHex)) { "$idHex is not a valid hex" }
val newObject = Note(idHex) return notes.getOrCreate(idHex) {
notes.putIfAbsent(idHex, newObject) ?: newObject Note(idHex)
} }
} }
fun checkGetOrCreateChannel(key: String): Channel? { fun checkGetOrCreateChannel(key: String): Channel? {
@@ -298,11 +279,9 @@ object LocalCache {
// we can't use naddr here because naddr might include relay info and // we can't use naddr here because naddr might include relay info and
// the preferred relay should not be part of the index. // the preferred relay should not be part of the index.
return addressables[key.toTag()] return addressables.getOrCreate(key.toTag()) {
?: run { AddressableNote(key)
val newObject = AddressableNote(key) }
addressables.putIfAbsent(key.toTag(), newObject) ?: newObject
}
} }
fun getOrCreateAddressableNote(key: ATag): AddressableNote { fun getOrCreateAddressableNote(key: ATag): AddressableNote {
@@ -771,9 +750,6 @@ object LocalCache {
if (version.event == null) { if (version.event == null) {
version.loadEvent(event, author, emptyList()) version.loadEvent(event, author, emptyList())
if (version.liveSet != null) {
updateListCache()
}
version.liveSet?.innerOts?.invalidateData() version.liveSet?.innerOts?.invalidateData()
} }
@@ -1433,9 +1409,6 @@ object LocalCache {
checkGetOrCreateNote(it)?.let { editedNote -> checkGetOrCreateNote(it)?.let { editedNote ->
modificationCache.remove(editedNote.idHex) modificationCache.remove(editedNote.idHex)
// must update list of Notes to quickly update the user. // must update list of Notes to quickly update the user.
if (editedNote.liveSet != null) {
updateListCache()
}
editedNote.liveSet?.innerModifications?.invalidateData() editedNote.liveSet?.innerModifications?.invalidateData()
} }
} }
@@ -1646,10 +1619,10 @@ object LocalCache {
} }
} }
return userListCache.filter { return users.filter { _, user: User ->
(it.anyNameStartsWith(username)) || (user.anyNameStartsWith(username)) ||
it.pubkeyHex.startsWith(username, true) || user.pubkeyHex.startsWith(username, true) ||
it.pubkeyNpub().startsWith(username, true) user.pubkeyNpub().startsWith(username, true)
} }
} }
@@ -1665,39 +1638,39 @@ object LocalCache {
} }
} }
return noteListCache.filter { return notes.filter { _, note ->
( (
it.event !is GenericRepostEvent && note.event !is GenericRepostEvent &&
it.event !is RepostEvent && note.event !is RepostEvent &&
it.event !is CommunityPostApprovalEvent && note.event !is CommunityPostApprovalEvent &&
it.event !is ReactionEvent && note.event !is ReactionEvent &&
it.event !is GiftWrapEvent && note.event !is GiftWrapEvent &&
it.event !is SealedGossipEvent && note.event !is SealedGossipEvent &&
it.event !is OtsEvent && note.event !is OtsEvent &&
it.event !is LnZapEvent && note.event !is LnZapEvent &&
it.event !is LnZapRequestEvent note.event !is LnZapRequestEvent
) && ) &&
( (
it.event?.content()?.contains(text, true) note.event?.content()?.contains(text, true)
?: false || ?: false ||
it.event?.matchTag1With(text) ?: false || note.event?.matchTag1With(text) ?: false ||
it.idHex.startsWith(text, true) || note.idHex.startsWith(text, true) ||
it.idNote().startsWith(text, true) note.idNote().startsWith(text, true)
) )
} + } +
addressables.values.filter { addressables.filter { _, addressable ->
( (
it.event !is GenericRepostEvent && addressable.event !is GenericRepostEvent &&
it.event !is RepostEvent && addressable.event !is RepostEvent &&
it.event !is CommunityPostApprovalEvent && addressable.event !is CommunityPostApprovalEvent &&
it.event !is ReactionEvent && addressable.event !is ReactionEvent &&
it.event !is GiftWrapEvent && addressable.event !is GiftWrapEvent &&
it.event !is LnZapEvent && addressable.event !is LnZapEvent &&
it.event !is LnZapRequestEvent addressable.event !is LnZapRequestEvent
) && ) &&
( (
it.event?.content()?.contains(text, true) addressable.event?.content()?.contains(text, true)
?: false || it.event?.matchTag1With(text) ?: false || it.idHex.startsWith(text, true) ?: false || addressable.event?.matchTag1With(text) ?: false || addressable.idHex.startsWith(text, true)
) )
} }
} }
@@ -1720,17 +1693,15 @@ object LocalCache {
suspend fun findStatusesForUser(user: User): ImmutableList<AddressableNote> { suspend fun findStatusesForUser(user: User): ImmutableList<AddressableNote> {
checkNotInMainThread() checkNotInMainThread()
return addressables return addressables.filter { _, it ->
.filter { val noteEvent = it.event
val noteEvent = it.value.event (
( noteEvent is StatusEvent &&
noteEvent is StatusEvent && noteEvent.pubKey == user.pubkeyHex &&
noteEvent.pubKey == user.pubkeyHex && !noteEvent.isExpired() &&
!noteEvent.isExpired() && noteEvent.content.isNotBlank()
noteEvent.content.isNotBlank() )
) }
}
.values
.sortedWith(compareBy({ it.event?.expiration() ?: it.event?.createdAt() }, { it.idHex })) .sortedWith(compareBy({ it.event?.expiration() ?: it.event?.createdAt() }, { it.idHex }))
.reversed() .reversed()
.toImmutableList() .toImmutableList()
@@ -1742,7 +1713,7 @@ object LocalCache {
var minTime: Long? = null var minTime: Long? = null
val time = TimeUtils.now() val time = TimeUtils.now()
noteListCache.forEach { item -> notes.forEach { _, item ->
val noteEvent = item.event val noteEvent = item.event
if ((noteEvent is OtsEvent && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time))) { if ((noteEvent is OtsEvent && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time))) {
noteEvent.verifiedTime?.let { stampedTime -> noteEvent.verifiedTime?.let { stampedTime ->
@@ -1774,7 +1745,7 @@ object LocalCache {
val time = TimeUtils.now() val time = TimeUtils.now()
val newNotes = val newNotes =
noteListCache.filter { item -> notes.filter { _, item ->
val noteEvent = item.event val noteEvent = item.event
noteEvent is TextNoteModificationEvent && noteEvent.pubKey == originalAuthor && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time) noteEvent is TextNoteModificationEvent && noteEvent.pubKey == originalAuthor && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time)
@@ -1786,11 +1757,9 @@ object LocalCache {
} }
fun cleanObservers() { fun cleanObservers() {
noteListCache.forEach { it.clearLive() } notes.forEach { _, it -> it.clearLive() }
addressables.forEach { _, it -> it.clearLive() }
addressables.forEach { it.value.clearLive() } users.forEach { _, it -> it.clearLive() }
userListCache.forEach { it.clearLive() }
} }
fun pruneOldAndHiddenMessages(account: Account) { fun pruneOldAndHiddenMessages(account: Account) {
@@ -1816,8 +1785,8 @@ object LocalCache {
} }
} }
userListCache.forEach { userPair -> users.forEach { _, user ->
userPair.privateChatrooms.values.map { user.privateChatrooms.values.map {
val toBeRemoved = it.pruneMessagesToTheLatestOnly() val toBeRemoved = it.pruneMessagesToTheLatestOnly()
val childrenToBeRemoved = mutableListOf<Note>() val childrenToBeRemoved = mutableListOf<Note>()
@@ -1832,7 +1801,7 @@ object LocalCache {
if (toBeRemoved.size > 1) { if (toBeRemoved.size > 1) {
println( println(
"PRUNE: ${toBeRemoved.size} private messages with ${userPair.toBestDisplayName()} removed. ${it.roomMessages.size} kept", "PRUNE: ${toBeRemoved.size} private messages with ${user.toBestDisplayName()} removed. ${it.roomMessages.size} kept",
) )
} }
} }
@@ -1841,21 +1810,20 @@ object LocalCache {
fun prunePastVersionsOfReplaceables() { fun prunePastVersionsOfReplaceables() {
val toBeRemoved = val toBeRemoved =
noteListCache notes.filter { _, note ->
.filter { val noteEvent = note.event
val noteEvent = it.event if (noteEvent is AddressableEvent) {
if (noteEvent is AddressableEvent) { noteEvent.createdAt() <
noteEvent.createdAt() < (addressables.get(noteEvent.address().toTag())?.event?.createdAt() ?: 0)
(addressables[noteEvent.address().toTag()]?.event?.createdAt() ?: 0) } else {
} else { false
false
}
} }
}
val childrenToBeRemoved = mutableListOf<Note>() val childrenToBeRemoved = mutableListOf<Note>()
toBeRemoved.forEach { toBeRemoved.forEach {
val newerVersion = addressables[(it.event as? AddressableEvent)?.address()?.toTag()] val newerVersion = (it.event as? AddressableEvent)?.address()?.toTag()?.let { tag -> addressables.get(tag) }
if (newerVersion != null) { if (newerVersion != null) {
it.moveAllReferencesTo(newerVersion) it.moveAllReferencesTo(newerVersion)
} }
@@ -1875,23 +1843,22 @@ object LocalCache {
checkNotInMainThread() checkNotInMainThread()
val toBeRemoved = val toBeRemoved =
noteListCache notes.filter { _, note ->
.filter { (
( (note.event is TextNoteEvent && !note.isNewThread()) ||
(it.event is TextNoteEvent && !it.isNewThread()) || note.event is ReactionEvent ||
it.event is ReactionEvent || note.event is LnZapEvent ||
it.event is LnZapEvent || note.event is LnZapRequestEvent ||
it.event is LnZapRequestEvent || note.event is ReportEvent ||
it.event is ReportEvent || note.event is GenericRepostEvent
it.event is GenericRepostEvent ) &&
) && note.replyTo?.any { it.liveSet?.isInUse() == true } != true &&
it.replyTo?.any { it.liveSet?.isInUse() == true } != true && note.liveSet?.isInUse() != true && // don't delete if observing.
it.liveSet?.isInUse() != true && // don't delete if observing. note.author?.pubkeyHex !in
it.author?.pubkeyHex !in accounts && // don't delete if it is the logged in account
accounts && // don't delete if it is the logged in account note.event?.isTaggedUsers(accounts) !=
it.event?.isTaggedUsers(accounts) != true // don't delete if it's a notification to the logged in user
true // don't delete if it's a notification to the logged in user }
}
val childrenToBeRemoved = mutableListOf<Note>() val childrenToBeRemoved = mutableListOf<Note>()
@@ -1958,7 +1925,7 @@ object LocalCache {
checkNotInMainThread() checkNotInMainThread()
val now = TimeUtils.now() val now = TimeUtils.now()
val toBeRemoved = noteListCache.filter { it.event?.isExpirationBefore(now) == true } val toBeRemoved = notes.filter { _, it -> it.event?.isExpirationBefore(now) == true }
val childrenToBeRemoved = mutableListOf<Note>() val childrenToBeRemoved = mutableListOf<Note>()
@@ -1983,11 +1950,7 @@ object LocalCache {
account.liveHiddenUsers.value account.liveHiddenUsers.value
?.hiddenUsers ?.hiddenUsers
?.map { userHex -> ?.map { userHex ->
( (notes.filter { _, it -> it.event?.pubKey() == userHex } + addressables.filter { _, it -> it.event?.pubKey() == userHex }).toSet()
noteListCache.filter { it.event?.pubKey() == userHex } +
addressables.values.filter { it.event?.pubKey() == userHex }
)
.toSet()
} }
?.flatten() ?.flatten()
?: emptyList() ?: emptyList()
@@ -2006,13 +1969,13 @@ object LocalCache {
checkNotInMainThread() checkNotInMainThread()
var removingContactList = 0 var removingContactList = 0
userListCache.forEach { users.forEach { _, user ->
if ( if (
it.pubkeyHex !in loggedIn && user.pubkeyHex !in loggedIn &&
(it.liveSet == null || it.liveSet?.isInUse() == false) && (user.liveSet == null || user.liveSet?.isInUse() == false) &&
it.latestContactList != null user.latestContactList != null
) { ) {
it.latestContactList = null user.latestContactList = null
removingContactList++ removingContactList++
} }
} }
@@ -2172,7 +2135,6 @@ class LocalCacheLiveData {
fun invalidateData(newNote: Note) { fun invalidateData(newNote: Note) {
bundler.invalidateList(newNote) { bundler.invalidateList(newNote) {
bundledNewNotes -> bundledNewNotes ->
LocalCache.updateListCache()
_newEventBundles.emit(bundledNewNotes) _newEventBundles.emit(bundledNewNotes)
} }
} }
@@ -354,7 +354,7 @@ class User(val pubkeyHex: String) {
} }
suspend fun transientFollowerCount(): Int { suspend fun transientFollowerCount(): Int {
return LocalCache.userListCache.count { it.latestContactList?.isTaggedUser(pubkeyHex) ?: false } return LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
} }
fun cachedFollowingKeySet(): Set<HexKey> { fun cachedFollowingKeySet(): Set<HexKey> {
@@ -378,7 +378,7 @@ class User(val pubkeyHex: String) {
} }
suspend fun cachedFollowerCount(): Int { suspend fun cachedFollowerCount(): Int {
return LocalCache.userListCache.count { it.latestContactList?.isTaggedUser(pubkeyHex) ?: false } return LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
} }
fun hasSentMessagesTo(key: ChatroomKey?): Boolean { fun hasSentMessagesTo(key: ChatroomKey?): Boolean {
@@ -21,8 +21,8 @@
package com.vitorpamplona.amethyst.service package com.vitorpamplona.amethyst.service
import android.util.LruCache import android.util.LruCache
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.commons.RichTextViewerState import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
import com.vitorpamplona.quartz.events.ImmutableListOfLists import com.vitorpamplona.quartz.events.ImmutableListOfLists
object CachedRichTextParser { object CachedRichTextParser {
@@ -40,6 +40,8 @@ object NostrVideoDataSource : NostrDataSource("VideoFeed") {
var job: Job? = null var job: Job? = null
val SUPPORTED_VIDEO_MIME_TYPES = listOf("image/jpeg", "image/gif", "image/png", "image/webp", "video/mp4", "video/mpeg", "video/webm", "audio/aac", "audio/mpeg", "audio/webm", "audio/wav")
override fun start() { override fun start() {
job?.cancel() job?.cancel()
job = job =
@@ -68,6 +70,7 @@ object NostrVideoDataSource : NostrDataSource("VideoFeed") {
authors = follows, authors = follows,
kinds = listOf(FileHeaderEvent.KIND, FileStorageHeaderEvent.KIND), kinds = listOf(FileHeaderEvent.KIND, FileStorageHeaderEvent.KIND),
limit = 200, limit = 200,
tags = mapOf("m" to SUPPORTED_VIDEO_MIME_TYPES),
since = since =
latestEOSEs.users[account.userProfile()] latestEOSEs.users[account.userProfile()]
?.followList ?.followList
@@ -93,6 +96,7 @@ object NostrVideoDataSource : NostrDataSource("VideoFeed") {
hashToLoad hashToLoad
.map { listOf(it, it.lowercase(), it.uppercase(), it.capitalize()) } .map { listOf(it, it.lowercase(), it.uppercase(), it.capitalize()) }
.flatten(), .flatten(),
"m" to SUPPORTED_VIDEO_MIME_TYPES,
), ),
limit = 100, limit = 100,
since = since =
@@ -120,6 +124,7 @@ object NostrVideoDataSource : NostrDataSource("VideoFeed") {
hashToLoad hashToLoad
.map { listOf(it, it.lowercase(), it.uppercase(), it.capitalize()) } .map { listOf(it, it.lowercase(), it.uppercase(), it.capitalize()) }
.flatten(), .flatten(),
"m" to SUPPORTED_VIDEO_MIME_TYPES,
), ),
limit = 100, limit = 100,
since = since =
@@ -46,7 +46,7 @@ class MultiPlayerPlaybackManager(
private val playingMap = mutableMapOf<String, MediaSession>() private val playingMap = mutableMapOf<String, MediaSession>()
private val cache = private val cache =
object : LruCache<String, MediaSession>(4) { // up to 4 videos in the screen at the same time object : LruCache<String, MediaSession>(10) { // up to 10 videos in the screen at the same time
override fun entryRemoved( override fun entryRemoved(
evicted: Boolean, evicted: Boolean,
key: String?, key: String?,
@@ -98,7 +98,7 @@ object Client : RelayPool.Listener {
checkNotInMainThread() checkNotInMainThread()
subscriptions = subscriptions + Pair(subscriptionId, filters) subscriptions = subscriptions + Pair(subscriptionId, filters)
RelayPool.sendFilter(subscriptionId) RelayPool.sendFilter(subscriptionId, filters)
} }
fun sendFilterOnlyIfDisconnected( fun sendFilterOnlyIfDisconnected(
@@ -134,7 +134,7 @@ object Client : RelayPool.Listener {
newSporadicRelay( newSporadicRelay(
relay, relay,
feedTypes, feedTypes,
onConnected = { relay -> relay.send(signedEvent) }, onConnected = { myRelay -> myRelay.send(signedEvent) },
onDone = onDone, onDone = onDone,
) )
} }
@@ -152,7 +152,9 @@ object Client : RelayPool.Listener {
RelayPool.addRelay(relay) RelayPool.addRelay(relay)
relay.connectAndRun { relay.connectAndRun {
allSubscriptions().forEach { relay.sendFilter(requestId = it) } allSubscriptions().forEach {
relay.sendFilter(it.key, it.value)
}
onConnected(relay) onConnected(relay)
@@ -264,8 +266,8 @@ object Client : RelayPool.Listener {
listeners = listeners.minus(listener) listeners = listeners.minus(listener)
} }
fun allSubscriptions(): Set<String> { fun allSubscriptions(): Map<String, List<TypedFilter>> {
return subscriptions.keys return subscriptions
} }
fun getSubscriptionFilters(subId: String): List<TypedFilter> { fun getSubscriptionFilters(subId: String): List<TypedFilter> {
@@ -344,16 +344,15 @@ class Relay(
afterEOSEPerSubscription = LinkedHashMap(afterEOSEPerSubscription.size) afterEOSEPerSubscription = LinkedHashMap(afterEOSEPerSubscription.size)
} }
fun sendFilter(requestId: String) { fun sendFilter(
requestId: String,
filters: List<TypedFilter>,
) {
checkNotInMainThread() checkNotInMainThread()
if (read) { if (read) {
if (isConnected()) { if (isConnected()) {
if (isReady) { if (isReady) {
val filters =
Client.getSubscriptionFilters(requestId).filter { filter ->
activeTypes.any { it in filter.types }
}
if (filters.isNotEmpty()) { if (filters.isNotEmpty()) {
val request = val request =
filters.joinToStringLimited( filters.joinToStringLimited(
@@ -423,7 +422,14 @@ class Relay(
fun renewFilters() { fun renewFilters() {
// Force update all filters after AUTH. // Force update all filters after AUTH.
Client.allSubscriptions().forEach { sendFilter(requestId = it) } Client.allSubscriptions().forEach {
val filters =
it.value.filter { filter ->
activeTypes.any { it in filter.types }
}
sendFilter(requestId = it.key, filters)
}
} }
fun send(signedEvent: EventInterface) { fun send(signedEvent: EventInterface) {
@@ -77,8 +77,11 @@ object RelayPool : Relay.Listener {
relays.forEach { it.connect() } relays.forEach { it.connect() }
} }
fun sendFilter(subscriptionId: String) { fun sendFilter(
relays.forEach { it.sendFilter(subscriptionId) } subscriptionId: String,
filters: List<TypedFilter>,
) {
relays.forEach { it.sendFilter(subscriptionId, filters) }
} }
fun connectAndSendFiltersIfDisconnected() { fun connectAndSendFiltersIfDisconnected() {
@@ -87,7 +87,7 @@ import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
import com.vitorpamplona.amethyst.ui.components.BechLink import com.vitorpamplona.amethyst.ui.components.BechLink
@@ -31,8 +31,8 @@ import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
@@ -125,7 +125,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState import com.google.accompanist.permissions.rememberPermissionState
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.Nip96MediaServers import com.vitorpamplona.amethyst.service.Nip96MediaServers
@@ -35,8 +35,8 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.fonfon.kgeohash.toGeoHash import com.fonfon.kgeohash.toGeoHash
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
@@ -44,7 +44,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.ExpandableTextCutOffCalculator import com.vitorpamplona.amethyst.commons.richtext.ExpandableTextCutOffCalculator
import com.vitorpamplona.amethyst.ui.note.getGradient import com.vitorpamplona.amethyst.ui.note.getGradient
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
@@ -27,9 +27,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
import com.vitorpamplona.amethyst.commons.MediaUrlVideo
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.components
import android.util.Log import android.util.Log
import android.util.Patterns import android.util.Patterns
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.quartz.encoders.ATag import com.vitorpamplona.quartz.encoders.ATag
@@ -69,24 +69,24 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.halilibo.richtext.markdown.Markdown import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.markdown.MarkdownParseOptions import com.halilibo.richtext.markdown.MarkdownParseOptions
import com.halilibo.richtext.ui.material3.Material3RichText import com.halilibo.richtext.ui.material3.Material3RichText
import com.vitorpamplona.amethyst.commons.BechSegment import com.vitorpamplona.amethyst.commons.richtext.BechSegment
import com.vitorpamplona.amethyst.commons.CashuSegment import com.vitorpamplona.amethyst.commons.richtext.CashuSegment
import com.vitorpamplona.amethyst.commons.EmailSegment import com.vitorpamplona.amethyst.commons.richtext.EmailSegment
import com.vitorpamplona.amethyst.commons.EmojiSegment import com.vitorpamplona.amethyst.commons.richtext.EmojiSegment
import com.vitorpamplona.amethyst.commons.HashIndexEventSegment import com.vitorpamplona.amethyst.commons.richtext.HashIndexEventSegment
import com.vitorpamplona.amethyst.commons.HashIndexUserSegment import com.vitorpamplona.amethyst.commons.richtext.HashIndexUserSegment
import com.vitorpamplona.amethyst.commons.HashTagSegment import com.vitorpamplona.amethyst.commons.richtext.HashTagSegment
import com.vitorpamplona.amethyst.commons.ImageSegment import com.vitorpamplona.amethyst.commons.richtext.ImageSegment
import com.vitorpamplona.amethyst.commons.InvoiceSegment import com.vitorpamplona.amethyst.commons.richtext.InvoiceSegment
import com.vitorpamplona.amethyst.commons.LinkSegment import com.vitorpamplona.amethyst.commons.richtext.LinkSegment
import com.vitorpamplona.amethyst.commons.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.PhoneSegment import com.vitorpamplona.amethyst.commons.richtext.PhoneSegment
import com.vitorpamplona.amethyst.commons.RegularTextSegment import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.commons.RichTextViewerState import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
import com.vitorpamplona.amethyst.commons.SchemelessUrlSegment import com.vitorpamplona.amethyst.commons.richtext.SchemelessUrlSegment
import com.vitorpamplona.amethyst.commons.Segment import com.vitorpamplona.amethyst.commons.richtext.Segment
import com.vitorpamplona.amethyst.commons.WithdrawSegment import com.vitorpamplona.amethyst.commons.richtext.WithdrawSegment
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.HashtagIcon import com.vitorpamplona.amethyst.model.HashtagIcon
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
@@ -30,7 +30,7 @@ import coil.fetch.Fetcher
import coil.fetch.SourceResult import coil.fetch.SourceResult
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.request.Options import coil.request.Options
import com.vitorpamplona.amethyst.commons.Robohash import com.vitorpamplona.amethyst.commons.robohash.Robohash
import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.checkNotInMainThread
import okio.buffer import okio.buffer
import okio.source import okio.source
@@ -49,7 +49,7 @@ import coil.fetch.FetchResult
import coil.fetch.Fetcher import coil.fetch.Fetcher
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.request.Options import coil.request.Options
import com.vitorpamplona.amethyst.commons.CachedRobohash import com.vitorpamplona.amethyst.commons.robohash.CachedRobohash
import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.theme.isLight import com.vitorpamplona.amethyst.ui.theme.isLight
import java.util.Base64 import java.util.Base64
@@ -106,13 +106,13 @@ import coil.compose.AsyncImage
import coil.compose.AsyncImagePainter import coil.compose.AsyncImagePainter
import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.BaseMediaContent import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
import com.vitorpamplona.amethyst.commons.MediaLocalImage import com.vitorpamplona.amethyst.commons.richtext.MediaLocalImage
import com.vitorpamplona.amethyst.commons.MediaLocalVideo import com.vitorpamplona.amethyst.commons.richtext.MediaLocalVideo
import com.vitorpamplona.amethyst.commons.MediaPreloadedContent import com.vitorpamplona.amethyst.commons.richtext.MediaPreloadedContent
import com.vitorpamplona.amethyst.commons.MediaUrlContent import com.vitorpamplona.amethyst.commons.richtext.MediaUrlContent
import com.vitorpamplona.amethyst.commons.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.MediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
import com.vitorpamplona.amethyst.service.BlurHashRequester import com.vitorpamplona.amethyst.service.BlurHashRequester
import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.actions.CloseButton
import com.vitorpamplona.amethyst.ui.actions.InformationDialog import com.vitorpamplona.amethyst.ui.actions.InformationDialog
@@ -45,7 +45,6 @@ class BookmarkPrivateFeedFilter(val account: Account) : FeedFilter<Note>() {
return notes return notes
.plus(addresses) .plus(addresses)
.toSet() .toSet()
.sortedWith(compareBy({ it.createdAt() }, { it.idHex })) .sortedWith(DefaultFeedOrder)
.reversed()
} }
} }
@@ -40,7 +40,6 @@ class BookmarkPublicFeedFilter(val account: Account) : FeedFilter<Note>() {
return notes return notes
.plus(addresses) .plus(addresses)
.toSet() .toSet()
.sortedWith(compareBy({ it.createdAt() }, { it.idHex })) .sortedWith(DefaultFeedOrder)
.reversed()
} }
} }
@@ -44,6 +44,6 @@ class ChannelFeedFilter(val channel: Channel, val account: Account) : AdditiveFe
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
} }
@@ -47,6 +47,6 @@ class ChatroomFeedFilter(val withUser: ChatroomKey, val account: Account) :
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
} }
@@ -197,6 +197,6 @@ class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter<Not
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
} }
@@ -138,6 +138,6 @@ class ChatroomListNewFeedFilter(val account: Account) : AdditiveFeedFilter<Note>
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
} }
@@ -24,16 +24,22 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
class CommunityFeedFilter(val note: AddressableNote, val account: Account) : class CommunityFeedFilter(val note: AddressableNote, val account: Account) : AdditiveFeedFilter<Note>() {
AdditiveFeedFilter<Note>() {
override fun feedKey(): String { override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + note.idHex return account.userProfile().pubkeyHex + "-" + note.idHex
} }
override fun feed(): List<Note> { override fun feed(): List<Note> {
return sort(innerApplyFilter(LocalCache.noteListCache)) val myPubKey = account.userProfile().pubkeyHex
val result =
LocalCache.notes.mapFlattenIntoSet { _, it ->
filterMap(it, myPubKey)
}
return sort(result)
} }
override fun applyFilter(collection: Set<Note>): Set<Note> { override fun applyFilter(collection: Set<Note>): Set<Note> {
@@ -41,31 +47,36 @@ class CommunityFeedFilter(val note: AddressableNote, val account: Account) :
} }
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> { private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val myUnapprovedPosts = val myPubKey = account.userProfile().pubkeyHex
collection
.asSequence()
.filter { it.event is CommunityPostApprovalEvent } // Only Approvals
.filter {
it.author?.pubkeyHex == account.userProfile().pubkeyHex
} // made by the logged in user
.filter { it.event?.isTaggedAddressableNote(note.idHex) == true } // for this community
.filter { it.isNewThread() } // check if it is a new thread
.toSet()
val approvedPosts = return collection.mapNotNull {
collection filterMap(it, myPubKey)
.asSequence() }.flatten().toSet()
.filter { it.event is CommunityPostApprovalEvent } // Only Approvals }
.filter { it.event?.isTaggedAddressableNote(note.idHex) == true } // Of the given community
.mapNotNull { it.replyTo }
.flatten() // get approved posts
.filter { it.isNewThread() } // check if it is a new thread
.toSet()
return myUnapprovedPosts + approvedPosts private fun filterMap(
note: Note,
myPubKey: HexKey,
): List<Note>? {
return if (
// Only Approvals
note.event is CommunityPostApprovalEvent &&
// Of the given community
note.event?.isTaggedAddressableNote(this.note.idHex) == true
) {
// if it is my post, bring on
if (note.author?.pubkeyHex == myPubKey && note.isNewThread()) {
listOf(note)
} else {
// brings the actual posts, not the approvals
note.replyTo?.filter { it.isNewThread() }
}
} else {
null
}
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
} }
@@ -20,36 +20,6 @@
*/ */
package com.vitorpamplona.amethyst.ui.dal package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.OnlineChecker
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_LIVE
class DiscoverLiveNowFeedFilter( val DefaultFeedOrder = compareBy<Note>({ it.createdAt() }, { it.idHex }).reversed()
account: Account,
) : DiscoverLiveFeedFilter(account) {
override fun followList(): String {
// uses follows by default, but other lists if they were selected in the top bar
val currentList = super.followList()
return if (currentList == GLOBAL_FOLLOWS) {
KIND3_FOLLOWS
} else {
currentList
}
}
override fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val allItems = super.innerApplyFilter(collection)
val onlineOnly =
allItems.filter {
val noteEvent = it.event as? LiveActivitiesEvent
noteEvent?.status() == STATUS_LIVE && OnlineChecker.isOnline(noteEvent.streaming())
}
return onlineOnly.toSet()
}
}
@@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst.ui.dal package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ParticipantListBuilder import com.vitorpamplona.amethyst.model.ParticipantListBuilder
@@ -29,7 +28,6 @@ import com.vitorpamplona.quartz.events.ChannelCreateEvent
import com.vitorpamplona.quartz.events.IsInPublicChatChannel import com.vitorpamplona.quartz.events.IsInPublicChatChannel
import com.vitorpamplona.quartz.events.MuteListEvent import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() { open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String { override fun feedKey(): String {
@@ -56,39 +54,34 @@ open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Not
return innerApplyFilter(collection) return innerApplyFilter(collection)
} }
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
userHex = account.userProfile().pubkeyHex,
selectedListName = account.defaultDiscoveryFollowList.value,
followLists = account.liveDiscoveryFollowLists.value,
hiddenUsers = account.flowHiddenUsers.value,
)
}
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> { protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val now = TimeUtils.now() val params = buildFilterParams(account)
val isGlobal = account.defaultDiscoveryFollowList.value == GLOBAL_FOLLOWS
val isHiddenList = showHiddenKey()
val followingKeySet = account.liveDiscoveryFollowLists.value?.users ?: emptySet() return collection.mapNotNullTo(HashSet()) { note ->
val followingTagSet = account.liveDiscoveryFollowLists.value?.hashtags ?: emptySet() // note event here will never be null
val followingGeohashSet = account.liveDiscoveryFollowLists.value?.geotags ?: emptySet() val noteEvent = note.event
if (noteEvent is ChannelCreateEvent && params.match(noteEvent)) {
val createEvents = collection.filter { it.event is ChannelCreateEvent } note
val anyOtherChannelEvent = } else if (noteEvent is IsInPublicChatChannel) {
collection val channel = noteEvent.channel()?.let { LocalCache.checkGetOrCreateNote(it) }
.asSequence() if (channel != null && (channel.event == null || params.match(channel.event))) {
.filter { it.event is IsInPublicChatChannel } channel
.mapNotNull { (it.event as? IsInPublicChatChannel)?.channel() } } else {
.mapNotNull { LocalCache.checkGetOrCreateNote(it) } null
.toSet()
val activities =
(createEvents + anyOtherChannelEvent)
.asSequence()
// .filter { it.event is ChannelCreateEvent } // Event heads might not be loaded yet.
.filter {
isGlobal ||
it.author?.pubkeyHex in followingKeySet ||
it.event?.isTaggedHashes(followingTagSet) == true ||
it.event?.isTaggedGeoHashes(followingGeohashSet) == true
} }
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true } } else {
.filter { (it.createdAt() ?: 0) <= now } null
.toSet() }
}
return activities
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
@@ -21,15 +21,14 @@
package com.vitorpamplona.amethyst.ui.dal package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ParticipantListBuilder import com.vitorpamplona.amethyst.model.ParticipantListBuilder
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.events.MuteListEvent import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() { open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String { override fun feedKey(): String {
@@ -44,9 +43,27 @@ open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilte
} }
override fun feed(): List<Note> { override fun feed(): List<Note> {
val allNotes = LocalCache.addressables.values val filterParams =
FilterByListParams.create(
userHex = account.userProfile().pubkeyHex,
selectedListName = account.defaultDiscoveryFollowList.value,
followLists = account.liveDiscoveryFollowLists.value,
hiddenUsers = account.flowHiddenUsers.value,
)
val notes = innerApplyFilter(allNotes) // Here we only need to look for CommunityDefinition Events
val notes =
LocalCache.addressables.mapNotNullIntoSet { key, note ->
val noteEvent = note.event
if (noteEvent == null && shouldInclude(ATag.parseAtagUnckecked(key), filterParams)) {
// send unloaded communities to the screen
note
} else if (noteEvent is CommunityDefinitionEvent && filterParams.match(noteEvent)) {
note
} else {
null
}
}
return sort(notes) return sort(notes)
} }
@@ -56,41 +73,44 @@ open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilte
} }
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> { protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val now = TimeUtils.now() // here, we need to look for CommunityDefinition in new collection AND new CommunityDefinition from Post Approvals
val isGlobal = account.defaultDiscoveryFollowList.value == GLOBAL_FOLLOWS val filterParams =
val isHiddenList = showHiddenKey() FilterByListParams.create(
userHex = account.userProfile().pubkeyHex,
selectedListName = account.defaultDiscoveryFollowList.value,
followLists = account.liveDiscoveryFollowLists.value,
hiddenUsers = account.flowHiddenUsers.value,
)
val followingKeySet = account.liveDiscoveryFollowLists.value?.users ?: emptySet() return collection.mapNotNull { note ->
val followingTagSet = account.liveDiscoveryFollowLists.value?.hashtags ?: emptySet() // note event here will never be null
val followingGeohashSet = account.liveDiscoveryFollowLists.value?.geotags ?: emptySet() val noteEvent = note.event
if (noteEvent is CommunityDefinitionEvent && filterParams.match(noteEvent)) {
listOf(note)
} else if (noteEvent is CommunityPostApprovalEvent) {
noteEvent.communities().mapNotNull {
val definitionNote = LocalCache.getOrCreateAddressableNote(it)
val definitionEvent = definitionNote.event
val createEvents = collection.filter { it.event is CommunityDefinitionEvent } if (definitionEvent == null && shouldInclude(it, filterParams)) {
val anyOtherCommunityEvent = definitionNote
collection } else if (definitionEvent is CommunityDefinitionEvent && filterParams.match(definitionEvent)) {
.asSequence() definitionNote
.filter { it.event is CommunityPostApprovalEvent } } else {
.mapNotNull { (it.event as? CommunityPostApprovalEvent)?.communities() } null
.flatten() }
.map { LocalCache.getOrCreateAddressableNote(it) }
.toSet()
val activities =
(createEvents + anyOtherCommunityEvent)
.asSequence()
.filter { it.event is CommunityDefinitionEvent }
.filter {
isGlobal ||
it.author?.pubkeyHex in followingKeySet ||
it.event?.isTaggedHashes(followingTagSet) == true ||
it.event?.isTaggedGeoHashes(followingGeohashSet) == true
} }
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true } } else {
.filter { (it.createdAt() ?: 0) <= now } null
.toSet() }
}.flatten().toSet()
return activities
} }
private fun shouldInclude(
aTag: ATag?,
params: FilterByListParams,
) = aTag != null && aTag.kind == CommunityDefinitionEvent.KIND && params.match(aTag)
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
val followingKeySet = val followingKeySet =
account.liveDiscoveryFollowLists.value?.users ?: account.liveKind3Follows.value.users account.liveDiscoveryFollowLists.value?.users ?: account.liveKind3Follows.value.users
@@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst.ui.dal package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ParticipantListBuilder import com.vitorpamplona.amethyst.model.ParticipantListBuilder
@@ -31,7 +30,6 @@ import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_LIVE
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_PLANNED import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_PLANNED
import com.vitorpamplona.quartz.events.MuteListEvent import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
open class DiscoverLiveFeedFilter( open class DiscoverLiveFeedFilter(
val account: Account, val account: Account,
@@ -64,33 +62,15 @@ open class DiscoverLiveFeedFilter(
} }
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> { protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val now = TimeUtils.now() val filterParams =
val isGlobal = account.defaultDiscoveryFollowList.value == GLOBAL_FOLLOWS FilterByListParams.create(
val isHiddenList = showHiddenKey() userHex = account.userProfile().pubkeyHex,
selectedListName = account.defaultDiscoveryFollowList.value,
followLists = account.liveDiscoveryFollowLists.value,
hiddenUsers = account.flowHiddenUsers.value,
)
val followingKeySet = account.liveDiscoveryFollowLists.value?.users ?: emptySet() return collection.filterTo(HashSet()) { it.event is LiveActivitiesEvent && filterParams.match(it.event) }
val followingTagSet = account.liveDiscoveryFollowLists.value?.hashtags ?: emptySet()
val followingGeohashSet = account.liveDiscoveryFollowLists.value?.geotags ?: emptySet()
val activities =
collection
.asSequence()
.filter { it.event is LiveActivitiesEvent }
.filter {
isGlobal ||
(it.event as LiveActivitiesEvent).participantsIntersect(followingKeySet) ||
it.event?.isTaggedHashes(
followingTagSet,
) == true ||
it.event?.isTaggedGeoHashes(
followingGeohashSet,
) == true
}
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true }
.filter { (it.createdAt() ?: 0) <= now }
.toSet()
return activities
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
@@ -21,13 +21,11 @@
package com.vitorpamplona.amethyst.ui.dal package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.events.ClassifiedsEvent import com.vitorpamplona.quartz.events.ClassifiedsEvent
import com.vitorpamplona.quartz.events.MuteListEvent import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
open class DiscoverMarketplaceFeedFilter( open class DiscoverMarketplaceFeedFilter(
val account: Account, val account: Account,
@@ -46,10 +44,13 @@ open class DiscoverMarketplaceFeedFilter(
} }
override fun feed(): List<Note> { override fun feed(): List<Note> {
val classifieds = val params = buildFilterParams(account)
LocalCache.addressables.filter { it.value.event is ClassifiedsEvent }.map { it.value }
val notes = innerApplyFilter(classifieds) val notes =
LocalCache.addressables.filterIntoSet { _, it ->
val noteEvent = it.event
noteEvent is ClassifiedsEvent && noteEvent.isWellFormed() && params.match(noteEvent)
}
return sort(notes) return sort(notes)
} }
@@ -58,35 +59,22 @@ open class DiscoverMarketplaceFeedFilter(
return innerApplyFilter(collection) return innerApplyFilter(collection)
} }
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
account.userProfile().pubkeyHex,
account.defaultDiscoveryFollowList.value,
account.liveDiscoveryFollowLists.value,
account.flowHiddenUsers.value,
)
}
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> { protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val now = TimeUtils.now() val params = buildFilterParams(account)
val isGlobal = account.defaultDiscoveryFollowList.value == GLOBAL_FOLLOWS
val isHiddenList = showHiddenKey()
val followingKeySet = account.liveDiscoveryFollowLists.value?.users ?: emptySet() return collection.filterTo(HashSet()) {
val followingTagSet = account.liveDiscoveryFollowLists.value?.hashtags ?: emptySet() val noteEvent = it.event
val followingGeohashSet = account.liveDiscoveryFollowLists.value?.geotags ?: emptySet() noteEvent is ClassifiedsEvent && noteEvent.isWellFormed() && params.match(noteEvent)
}
val activities =
collection
.asSequence()
.filter {
it.event is ClassifiedsEvent &&
it.event?.hasTagWithContent("image") == true &&
it.event?.hasTagWithContent("price") == true &&
it.event?.hasTagWithContent("title") == true
}
.filter {
isGlobal ||
it.author?.pubkeyHex in followingKeySet ||
it.event?.isTaggedHashes(followingTagSet) == true ||
it.event?.isTaggedGeoHashes(followingGeohashSet) == true
}
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true }
.filter { (it.createdAt() ?: 0) <= now }
.toSet()
return activities
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
@@ -0,0 +1,103 @@
/**
* 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.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.EventInterface
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
class FilterByListParams(
val isGlobal: Boolean,
val isHiddenList: Boolean,
val followLists: Account.LiveFollowLists?,
val hiddenLists: Account.LiveHiddenUsers,
val now: Long = TimeUtils.now(),
) {
fun isNotHidden(userHex: String) = !(hiddenLists.hiddenUsers.contains(userHex) || hiddenLists.spammers.contains(userHex))
fun isNotInTheFuture(noteEvent: Event) = noteEvent.createdAt <= now
fun isEventInList(noteEvent: Event): Boolean {
if (followLists == null) return false
return if (noteEvent is LiveActivitiesEvent) {
noteEvent.participantsIntersect(followLists.users) ||
noteEvent.isTaggedHashes(followLists.hashtags) ||
noteEvent.isTaggedGeoHashes(followLists.users) ||
noteEvent.isTaggedAddressableNotes(followLists.communities)
} else {
noteEvent.pubKey in followLists.users ||
noteEvent.isTaggedHashes(followLists.hashtags) ||
noteEvent.isTaggedGeoHashes(followLists.users) ||
noteEvent.isTaggedAddressableNotes(followLists.communities)
}
}
fun isATagInList(aTag: ATag): Boolean {
if (followLists == null) return false
return aTag.pubKeyHex in followLists.users
}
fun match(
noteEvent: EventInterface?,
isGlobalRelay: Boolean = true,
) = if (noteEvent is Event) match(noteEvent, isGlobalRelay) else false
fun match(
noteEvent: Event,
isGlobalRelay: Boolean = true,
) = ((isGlobal && isGlobalRelay) || isEventInList(noteEvent)) &&
(isHiddenList || isNotHidden(noteEvent.pubKey)) &&
isNotInTheFuture(noteEvent)
fun match(aTag: ATag?) =
aTag != null &&
(isGlobal || isATagInList(aTag)) &&
(isHiddenList || isNotHidden(aTag.pubKeyHex))
companion object {
fun showHiddenKey(
selectedListName: String,
userHex: String,
) = selectedListName == PeopleListEvent.blockListFor(userHex) || selectedListName == MuteListEvent.blockListFor(userHex)
fun create(
userHex: String,
selectedListName: String,
followLists: Account.LiveFollowLists?,
hiddenUsers: Account.LiveHiddenUsers,
): FilterByListParams {
return FilterByListParams(
isGlobal = selectedListName == GLOBAL_FOLLOWS,
isHiddenList = showHiddenKey(selectedListName, userHex),
followLists = followLists,
hiddenLists = hiddenUsers,
)
}
}
}
@@ -36,7 +36,12 @@ class GeoHashFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
} }
override fun feed(): List<Note> { override fun feed(): List<Note> {
return sort(innerApplyFilter(LocalCache.noteListCache)) val notes =
LocalCache.notes.filterIntoSet { _, it ->
acceptableEvent(it, tag)
}
return sort(notes)
} }
override fun applyFilter(collection: Set<Note>): Set<Note> { override fun applyFilter(collection: Set<Note>): Set<Note> {
@@ -44,25 +49,24 @@ class GeoHashFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
} }
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> { private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val myTag = tag ?: return emptySet() return collection.filterTo(HashSet<Note>()) { acceptableEvent(it, tag) }
}
return collection fun acceptableEvent(
.asSequence() it: Note,
.filter { geoTag: String,
( ): Boolean {
it.event is TextNoteEvent || return (
it.event is LongTextNoteEvent || it.event is TextNoteEvent ||
it.event is ChannelMessageEvent || it.event is LongTextNoteEvent ||
it.event is PrivateDmEvent || it.event is ChannelMessageEvent ||
it.event is PollNoteEvent || it.event is PrivateDmEvent ||
it.event is AudioHeaderEvent it.event is PollNoteEvent ||
) && it.event?.isTaggedGeoHash(myTag) == true it.event is AudioHeaderEvent
} ) && it.event?.isTaggedGeoHash(geoTag) == true && account.isAcceptable(it)
.filter { account.isAcceptable(it) }
.toSet()
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
} }
@@ -36,7 +36,12 @@ class HashtagFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
} }
override fun feed(): List<Note> { override fun feed(): List<Note> {
return sort(innerApplyFilter(LocalCache.noteListCache)) val notes =
LocalCache.notes.filterIntoSet { _, it ->
acceptableEvent(it, tag)
}
return sort(notes)
} }
override fun applyFilter(collection: Set<Note>): Set<Note> { override fun applyFilter(collection: Set<Note>): Set<Note> {
@@ -44,25 +49,24 @@ class HashtagFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
} }
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> { private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val myTag = tag ?: return emptySet() return collection.filterTo(HashSet<Note>()) { acceptableEvent(it, tag) }
}
return collection fun acceptableEvent(
.asSequence() it: Note,
.filter { hashTag: String,
( ): Boolean {
it.event is TextNoteEvent || return (
it.event is LongTextNoteEvent || it.event is TextNoteEvent ||
it.event is ChannelMessageEvent || it.event is LongTextNoteEvent ||
it.event is PrivateDmEvent || it.event is ChannelMessageEvent ||
it.event is PollNoteEvent || it.event is PrivateDmEvent ||
it.event is AudioHeaderEvent it.event is PollNoteEvent ||
) && it.event?.isTaggedHash(myTag) == true it.event is AudioHeaderEvent
} ) && it.event?.isTaggedHash(hashTag) == true && account.isAcceptable(it)
.filter { account.isAcceptable(it) }
.toSet()
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
} }
@@ -21,17 +21,14 @@
package com.vitorpamplona.amethyst.ui.dal package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.events.ChannelMessageEvent import com.vitorpamplona.quartz.events.ChannelMessageEvent
import com.vitorpamplona.quartz.events.DraftEvent
import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.events.MuteListEvent import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.events.PollNoteEvent import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.TextNoteEvent import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.utils.TimeUtils
class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() { class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String { override fun feedKey(): String {
@@ -39,56 +36,54 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Not
} }
override fun showHiddenKey(): Boolean { override fun showHiddenKey(): Boolean {
return account.defaultHomeFollowList.value == return account.defaultHomeFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) || account.defaultHomeFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
account.defaultHomeFollowList.value ==
MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
} }
override fun feed(): List<Note> { override fun feed(): List<Note> {
return sort(innerApplyFilter(LocalCache.noteListCache)) val filterParams = buildFilterParams(account)
return sort(
LocalCache.notes.filterIntoSet { _, it ->
acceptableEvent(it, filterParams)
},
)
} }
override fun applyFilter(collection: Set<Note>): Set<Note> { override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection) return innerApplyFilter(collection)
} }
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
userHex = account.userProfile().pubkeyHex,
selectedListName = account.defaultHomeFollowList.value,
followLists = account.liveHomeFollowLists.value,
hiddenUsers = account.flowHiddenUsers.value,
)
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> { private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val isGlobal = account.defaultHomeFollowList.value == GLOBAL_FOLLOWS val filterParams = buildFilterParams(account)
val isHiddenList = showHiddenKey()
val followingKeySet = account.liveHomeFollowLists.value?.users ?: emptySet() return collection.filterTo(HashSet()) {
val followingTagSet = account.liveHomeFollowLists.value?.hashtags ?: emptySet() acceptableEvent(it, filterParams)
val followingGeohashSet = account.liveHomeFollowLists.value?.geotags ?: emptySet() }
}
val now = TimeUtils.now() fun acceptableEvent(
it: Note,
return collection filterParams: FilterByListParams,
.asSequence() ): Boolean {
.filter { return (
( it.event is TextNoteEvent ||
it.event is TextNoteEvent || it.event is PollNoteEvent ||
it.event is PollNoteEvent || it.event is ChannelMessageEvent ||
it.event is ChannelMessageEvent || it.event is LiveActivitiesChatMessageEvent
it.event is LiveActivitiesChatMessageEvent || ) && filterParams.match(it.event) && !it.isNewThread()
it.event is DraftEvent
) &&
(
isGlobal ||
it.author?.pubkeyHex in followingKeySet ||
it.event?.isTaggedHashes(followingTagSet) ?: false ||
it.event?.isTaggedGeoHashes(followingGeohashSet) ?: false
) &&
// && account.isAcceptable(it) // This filter follows only. No need to check if
// acceptable
(isHiddenList || it.author?.let { !account.isHidden(it) } ?: true) &&
((it.event?.createdAt() ?: 0) < now) &&
!it.isNewThread()
}
.toSet()
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
} }
@@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst.ui.dal package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.events.AudioHeaderEvent import com.vitorpamplona.quartz.events.AudioHeaderEvent
@@ -35,7 +34,6 @@ import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.events.PollNoteEvent import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.RepostEvent import com.vitorpamplona.quartz.events.RepostEvent
import com.vitorpamplona.quartz.events.TextNoteEvent import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.utils.TimeUtils
class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() { class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String { override fun feedKey(): String {
@@ -43,69 +41,70 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
} }
override fun showHiddenKey(): Boolean { override fun showHiddenKey(): Boolean {
return account.defaultHomeFollowList.value == return account.defaultHomeFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) || account.defaultHomeFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
account.defaultHomeFollowList.value == }
MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
userHex = account.userProfile().pubkeyHex,
selectedListName = account.defaultHomeFollowList.value,
followLists = account.liveHomeFollowLists.value,
hiddenUsers = account.flowHiddenUsers.value,
)
} }
override fun feed(): List<Note> { override fun feed(): List<Note> {
val notes = innerApplyFilter(LocalCache.noteListCache, true) val gRelays = account.activeGlobalRelays().toSet()
val longFormNotes = innerApplyFilter(LocalCache.addressables.values, false) val filterParams = buildFilterParams(account)
val notes =
LocalCache.notes.filterIntoSet { _, note ->
// Avoids processing addressables twice.
(note.event?.kind() ?: 99999) < 10000 && acceptableEvent(note, gRelays, filterParams)
}
val longFormNotes =
LocalCache.addressables.filterIntoSet { _, note ->
acceptableEvent(note, gRelays, filterParams)
}
return sort(notes + longFormNotes) return sort(notes + longFormNotes)
} }
override fun applyFilter(collection: Set<Note>): Set<Note> { override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection, false) return innerApplyFilter(collection)
} }
private fun innerApplyFilter( private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
collection: Collection<Note>, val gRelays = account.activeGlobalRelays().toSet()
ignoreAddressables: Boolean, val filterParams = buildFilterParams(account)
): Set<Note> {
val isGlobal = account.defaultHomeFollowList.value == GLOBAL_FOLLOWS
val gRelays = account.activeGlobalRelays()
val isHiddenList = showHiddenKey()
val followingKeySet = account.liveHomeFollowLists.value?.users ?: emptySet() return collection.filterTo(HashSet()) {
val followingTagSet = account.liveHomeFollowLists.value?.hashtags ?: emptySet() acceptableEvent(it, gRelays, filterParams)
val followingGeohashSet = account.liveHomeFollowLists.value?.geotags ?: emptySet() }
val followingCommunities = account.liveHomeFollowLists.value?.communities ?: emptySet() }
val oneMinuteInTheFuture = TimeUtils.now() + (1 * 60) // one minute in the future. fun acceptableEvent(
it: Note,
return collection globalRelays: Set<String>,
.asSequence() filterParams: FilterByListParams,
.filter { it -> ): Boolean {
val noteEvent = it.event val noteEvent = it.event
val isGlobalRelay = it.relays.any { gRelays.contains(it.url) } val isGlobalRelay = it.relays.any { globalRelays.contains(it.url) }
( return (
noteEvent is TextNoteEvent || noteEvent is TextNoteEvent ||
noteEvent is ClassifiedsEvent || noteEvent is ClassifiedsEvent ||
noteEvent is RepostEvent || noteEvent is RepostEvent ||
noteEvent is GenericRepostEvent || noteEvent is GenericRepostEvent ||
noteEvent is LongTextNoteEvent || noteEvent is LongTextNoteEvent ||
noteEvent is PollNoteEvent || noteEvent is PollNoteEvent ||
noteEvent is HighlightEvent || noteEvent is HighlightEvent ||
noteEvent is AudioTrackEvent || noteEvent is AudioTrackEvent ||
noteEvent is AudioHeaderEvent noteEvent is AudioHeaderEvent
) && ) &&
(!ignoreAddressables || noteEvent.kind() < 10000) && filterParams.match(noteEvent, isGlobalRelay) &&
( it.isNewThread()
(isGlobal && isGlobalRelay) ||
it.author?.pubkeyHex in followingKeySet ||
noteEvent.isTaggedHashes(followingTagSet) ||
noteEvent.isTaggedGeoHashes(followingGeohashSet) ||
noteEvent.isTaggedAddressableNotes(followingCommunities)
) &&
// && account.isAcceptable(it) // This filter follows only. No need to check if
// acceptable
(isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true) &&
((it.event?.createdAt() ?: 0) < oneMinuteInTheFuture) &&
it.isNewThread()
}
.toSet()
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
@@ -115,6 +114,6 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
} else { } else {
it.idHex it.idHex
} }
}.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() }.sortedWith(DefaultFeedOrder)
} }
} }
@@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst.ui.dal package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.encoders.HexKey
@@ -35,7 +34,6 @@ import com.vitorpamplona.quartz.events.GiftWrapEvent
import com.vitorpamplona.quartz.events.GitIssueEvent import com.vitorpamplona.quartz.events.GitIssueEvent
import com.vitorpamplona.quartz.events.GitPatchEvent import com.vitorpamplona.quartz.events.GitPatchEvent
import com.vitorpamplona.quartz.events.HighlightEvent import com.vitorpamplona.quartz.events.HighlightEvent
import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.LnZapRequestEvent import com.vitorpamplona.quartz.events.LnZapRequestEvent
import com.vitorpamplona.quartz.events.MuteListEvent import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent import com.vitorpamplona.quartz.events.PeopleListEvent
@@ -54,8 +52,24 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
MuteListEvent.blockListFor(account.userProfile().pubkeyHex) MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
} }
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
userHex = account.userProfile().pubkeyHex,
selectedListName = account.defaultNotificationFollowList.value,
followLists = account.liveNotificationFollowLists.value,
hiddenUsers = account.flowHiddenUsers.value,
)
}
override fun feed(): List<Note> { override fun feed(): List<Note> {
return sort(innerApplyFilter(LocalCache.noteListCache)) val filterParams = buildFilterParams(account)
val notifications =
LocalCache.notes.filterIntoSet { _, note ->
acceptableEvent(note, filterParams)
}
return sort(notifications)
} }
override fun applyFilter(collection: Set<Note>): Set<Note> { override fun applyFilter(collection: Set<Note>): Set<Note> {
@@ -63,32 +77,31 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
} }
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> { private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val isGlobal = account.defaultNotificationFollowList.value == GLOBAL_FOLLOWS val filterParams = buildFilterParams(account)
val isHiddenList = showHiddenKey()
val followingKeySet = account.liveNotificationFollowLists.value?.users ?: emptySet() return collection.filterTo(HashSet()) { acceptableEvent(it, filterParams) }
}
val loggedInUser = account.userProfile() fun acceptableEvent(
val loggedInUserHex = loggedInUser.pubkeyHex it: Note,
filterParams: FilterByListParams,
): Boolean {
val loggedInUserHex = account.userProfile().pubkeyHex
return collection return it.event !is ChannelCreateEvent &&
.filterTo(HashSet()) { it.event !is ChannelMetadataEvent &&
it.event !is ChannelCreateEvent && it.event !is LnZapRequestEvent &&
it.event !is ChannelMetadataEvent && it.event !is BadgeDefinitionEvent &&
it.event !is LnZapRequestEvent && it.event !is BadgeProfilesEvent &&
it.event !is BadgeDefinitionEvent && it.event !is GiftWrapEvent &&
it.event !is BadgeProfilesEvent && (filterParams.isGlobal || filterParams.followLists?.users?.contains(it.author?.pubkeyHex) == true) &&
it.event !is GiftWrapEvent && it.event?.isTaggedUser(loggedInUserHex) ?: false &&
(it.event is LnZapEvent || it.author !== loggedInUser) && (filterParams.isHiddenList || it.author == null || !account.isHidden(it.author!!.pubkeyHex)) &&
(isGlobal || it.author?.pubkeyHex in followingKeySet) && tagsAnEventByUser(it, loggedInUserHex)
it.event?.isTaggedUser(loggedInUserHex) ?: false &&
(isHiddenList || it.author == null || !account.isHidden(it.author!!.pubkeyHex)) &&
tagsAnEventByUser(it, loggedInUserHex)
}
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
fun tagsAnEventByUser( fun tagsAnEventByUser(
@@ -31,7 +31,12 @@ class UserProfileAppRecommendationsFeedFilter(val user: User) : AdditiveFeedFilt
} }
override fun feed(): List<Note> { override fun feed(): List<Note> {
return sort(innerApplyFilter(LocalCache.addressables.values)) val recommendations =
LocalCache.addressables.mapFlattenIntoSet { _, note ->
filterMap(note)
}
return sort(recommendations)
} }
override fun applyFilter(collection: Set<Note>): Set<Note> { override fun applyFilter(collection: Set<Note>): Set<Note> {
@@ -39,26 +44,21 @@ class UserProfileAppRecommendationsFeedFilter(val user: User) : AdditiveFeedFilt
} }
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> { private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val recommendations = return collection.mapNotNull { filterMap(it) }.flatten().toSet()
collection }
.asSequence()
.filter { it.event is AppRecommendationEvent }
.mapNotNull {
val noteEvent = it.event as? AppRecommendationEvent
if (noteEvent != null && noteEvent.pubKey == user.pubkeyHex) {
noteEvent.recommendations()
} else {
null
}
}
.flatten()
.map { LocalCache.getOrCreateAddressableNote(it) }
.toSet()
return recommendations fun filterMap(it: Note): List<Note>? {
val noteEvent = it.event
if (noteEvent is AppRecommendationEvent) {
if (noteEvent.pubKey == user.pubkeyHex) {
return noteEvent.recommendations().map { LocalCache.getOrCreateAddressableNote(it) }
}
}
return null
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })) return collection.sortedWith(DefaultFeedOrder)
} }
} }
@@ -47,7 +47,6 @@ class UserProfileBookmarksFeedFilter(val user: User, val account: Account) : Fee
return (notes + addresses) return (notes + addresses)
.filter { account.isAcceptable(it) } .filter { account.isAcceptable(it) }
.sortedWith(compareBy({ it.createdAt() }, { it.idHex })) .sortedWith(DefaultFeedOrder)
.reversed()
} }
} }
@@ -36,7 +36,17 @@ class UserProfileConversationsFeedFilter(val user: User, val account: Account) :
} }
override fun feed(): List<Note> { override fun feed(): List<Note> {
return sort(innerApplyFilter(LocalCache.noteListCache)) val notes =
LocalCache.notes.filterIntoSet { _, it ->
acceptableEvent(it)
}
val longFormNotes =
LocalCache.addressables.filterIntoSet { _, it ->
acceptableEvent(it)
}
return sort(notes + longFormNotes)
} }
override fun applyFilter(collection: Set<Note>): Set<Note> { override fun applyFilter(collection: Set<Note>): Set<Note> {
@@ -44,23 +54,21 @@ class UserProfileConversationsFeedFilter(val user: User, val account: Account) :
} }
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> { private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
return collection return collection.filterTo(HashSet()) { acceptableEvent(it) }
.filter { }
it.author == user &&
( fun acceptableEvent(it: Note): Boolean {
it.event is TextNoteEvent || return it.author == user &&
it.event is PollNoteEvent || (
it.event is ChannelMessageEvent || it.event is TextNoteEvent ||
it.event is LiveActivitiesChatMessageEvent it.event is PollNoteEvent ||
) && it.event is ChannelMessageEvent ||
!it.isNewThread() && it.event is LiveActivitiesChatMessageEvent
account.isAcceptable(it) == true ) && !it.isNewThread() && account.isAcceptable(it)
}
.toSet()
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
override fun limit() = 200 override fun limit() = 200
@@ -30,7 +30,9 @@ class UserProfileFollowersFeedFilter(val user: User, val account: Account) : Fee
} }
override fun feed(): List<User> { override fun feed(): List<User> {
return LocalCache.userListCache.filter { it.isFollowing(user) && !account.isHidden(it) } return LocalCache.users.filter { _, it ->
it.isFollowing(user) && !account.isHidden(it)
}
} }
override fun limit() = 400 override fun limit() = 400
@@ -41,8 +41,15 @@ class UserProfileNewThreadFeedFilter(val user: User, val account: Account) :
} }
override fun feed(): List<Note> { override fun feed(): List<Note> {
val notes = innerApplyFilter(LocalCache.noteListCache) val notes =
val longFormNotes = innerApplyFilter(LocalCache.addressables.values) LocalCache.notes.filterIntoSet { _, it ->
acceptableEvent(it)
}
val longFormNotes =
LocalCache.addressables.filterIntoSet { _, it ->
acceptableEvent(it)
}
return sort(notes + longFormNotes) return sort(notes + longFormNotes)
} }
@@ -52,28 +59,26 @@ class UserProfileNewThreadFeedFilter(val user: User, val account: Account) :
} }
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> { private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
return collection return collection.filterTo(HashSet()) { acceptableEvent(it) }
.filter { }
it.author == user &&
( fun acceptableEvent(it: Note): Boolean {
it.event is TextNoteEvent || return it.author == user &&
it.event is ClassifiedsEvent || (
it.event is RepostEvent || it.event is TextNoteEvent ||
it.event is GenericRepostEvent || it.event is ClassifiedsEvent ||
it.event is LongTextNoteEvent || it.event is RepostEvent ||
it.event is PollNoteEvent || it.event is GenericRepostEvent ||
it.event is HighlightEvent || it.event is LongTextNoteEvent ||
it.event is AudioTrackEvent || it.event is PollNoteEvent ||
it.event is AudioHeaderEvent it.event is HighlightEvent ||
) && it.event is AudioTrackEvent ||
it.isNewThread() && it.event is AudioHeaderEvent
account.isAcceptable(it) == true ) && it.isNewThread() && account.isAcceptable(it)
}
.toSet()
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
override fun limit() = 200 override fun limit() = 200
@@ -44,7 +44,7 @@ class UserProfileReportsFeedFilter(val user: User) : AdditiveFeedFilter<Note>()
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
override fun limit() = 400 override fun limit() = 400
@@ -21,14 +21,12 @@
package com.vitorpamplona.amethyst.ui.dal package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.events.FileHeaderEvent import com.vitorpamplona.quartz.events.FileHeaderEvent
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
import com.vitorpamplona.quartz.events.MuteListEvent import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() { class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String { override fun feedKey(): String {
@@ -36,14 +34,17 @@ class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
} }
override fun showHiddenKey(): Boolean { override fun showHiddenKey(): Boolean {
return account.defaultStoriesFollowList.value == return account.defaultStoriesFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) || account.defaultStoriesFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
account.defaultStoriesFollowList.value ==
MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
} }
override fun feed(): List<Note> { override fun feed(): List<Note> {
val notes = innerApplyFilter(LocalCache.noteListCache) val params = buildFilterParams(account)
val notes =
LocalCache.notes.filterIntoSet { _, it ->
acceptableEvent(it, params)
}
return sort(notes) return sort(notes)
} }
@@ -53,36 +54,32 @@ class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
} }
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> { private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val now = TimeUtils.now() val params = buildFilterParams(account)
val isGlobal = account.defaultStoriesFollowList.value == GLOBAL_FOLLOWS
val isHiddenList =
account.defaultStoriesFollowList.value ==
PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultStoriesFollowList.value ==
MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
val followingKeySet = account.liveStoriesFollowLists.value?.users ?: emptySet() return collection.filterTo(HashSet()) { acceptableEvent(it, params) }
val followingTagSet = account.liveStoriesFollowLists.value?.hashtags ?: emptySet() }
val followingGeohashSet = account.liveStoriesFollowLists.value?.geotags ?: emptySet()
return collection fun acceptableEvent(
.asSequence() it: Note,
.filter { params: FilterByListParams,
(it.event is FileHeaderEvent && (it.event as FileHeaderEvent).hasUrl()) || ): Boolean {
it.event is FileStorageHeaderEvent val noteEvent = it.event
}
.filter { return ((noteEvent is FileHeaderEvent && noteEvent.hasUrl() && noteEvent.isImageOrVideo()) || (noteEvent is FileStorageHeaderEvent && noteEvent.isImageOrVideo())) &&
isGlobal || params.match(noteEvent) &&
it.author?.pubkeyHex in followingKeySet || account.isAcceptable(it)
(it.event?.isTaggedHashes(followingTagSet) ?: false) || }
(it.event?.isTaggedGeoHashes(followingGeohashSet) ?: false)
} fun buildFilterParams(account: Account): FilterByListParams {
.filter { isHiddenList || account.isAcceptable(it) } return FilterByListParams.create(
.filter { it.createdAt()!! <= now } userHex = account.userProfile().pubkeyHex,
.toSet() selectedListName = account.defaultStoriesFollowList.value,
followLists = account.liveStoriesFollowLists.value,
hiddenUsers = account.flowHiddenUsers.value,
)
} }
override fun sort(collection: Set<Note>): List<Note> { override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed() return collection.sortedWith(DefaultFeedOrder)
} }
} }
@@ -356,7 +356,9 @@ private fun RenderRoomTopBar(
RoomNameOnlyDisplay( RoomNameOnlyDisplay(
room, room,
Modifier.padding(start = 10.dp).weight(1f), Modifier
.padding(start = 10.dp)
.weight(1f),
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
accountViewModel.userProfile(), accountViewModel.userProfile(),
) )
@@ -504,7 +506,10 @@ fun GenericMainTopBar(
) { ) {
Box(Modifier) { Box(Modifier) {
Column( Column(
modifier = Modifier.fillMaxWidth().fillMaxHeight(), modifier =
Modifier
.fillMaxWidth()
.fillMaxHeight(),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
) { ) {
@@ -650,15 +655,15 @@ class FollowListViewModel(val account: Account) : ViewModel() {
val newFollowLists = val newFollowLists =
LocalCache.addressables LocalCache.addressables
.mapNotNull { .mapNotNull { _, addressableNote ->
val event = (it.value.event as? PeopleListEvent) val event = (addressableNote.event as? PeopleListEvent)
// Has to have an list // Has to have an list
if ( if (
event != null && event != null &&
event.pubKey == account.userProfile().pubkeyHex && event.pubKey == account.userProfile().pubkeyHex &&
(event.tags.size > 1 || event.content.length > 50) (event.tags.size > 1 || event.content.length > 50)
) { ) {
CodeName(event.address().toTag(), PeopleListName(it.value), CodeNameType.PEOPLE_LIST) CodeName(event.address().toTag(), PeopleListName(addressableNote), CodeNameType.PEOPLE_LIST)
} else { } else {
null null
} }
@@ -784,12 +789,14 @@ fun SimpleTextSpinner(
} }
Box( Box(
modifier = modifier =
Modifier.matchParentSize().clickable( Modifier
interactionSource = interactionSource, .matchParentSize()
indication = null, .clickable(
) { interactionSource = interactionSource,
optionsShowing = true indication = null,
}, ) {
optionsShowing = true
},
) )
} }
@@ -922,13 +929,7 @@ fun AmethystClickableIcon() {
fun debugState(context: Context) { fun debugState(context: Context) {
Client.allSubscriptions() Client.allSubscriptions()
.map { .forEach { Log.d("STATE DUMP", "${it.key} ${it.value.joinToString { it.filter.toJson() }}") }
"$it ${
Client.getSubscriptionFilters(it)
.joinToString { it.filter.toJson() }
}"
}
.forEach { Log.d("STATE DUMP", it) }
NostrAccountDataSource.printCounter() NostrAccountDataSource.printCounter()
NostrChannelDataSource.printCounter() NostrChannelDataSource.printCounter()
@@ -973,44 +974,44 @@ fun debugState(context: Context) {
Log.d( Log.d(
"STATE DUMP", "STATE DUMP",
"Notes: " + "Notes: " +
LocalCache.noteListCache.filter { it.liveSet != null }.size + LocalCache.notes.filter { _, it -> it.liveSet != null }.size +
" / " + " / " +
LocalCache.noteListCache.filter { it.event != null }.size + LocalCache.notes.filter { _, it -> it.event != null }.size +
" / " + " / " +
LocalCache.noteListCache.size, LocalCache.notes.size(),
) )
Log.d( Log.d(
"STATE DUMP", "STATE DUMP",
"Addressables: " + "Addressables: " +
LocalCache.addressables.filter { it.value.liveSet != null }.size + LocalCache.addressables.filter { _, it -> it.liveSet != null }.size +
" / " + " / " +
LocalCache.addressables.filter { it.value.event != null }.size + LocalCache.addressables.filter { _, it -> it.event != null }.size +
" / " + " / " +
LocalCache.addressables.size, LocalCache.addressables.size(),
) )
Log.d( Log.d(
"STATE DUMP", "STATE DUMP",
"Users: " + "Users: " +
LocalCache.userListCache.filter { it.liveSet != null }.size + LocalCache.users.filter { _, it -> it.liveSet != null }.size +
" / " + " / " +
LocalCache.userListCache.filter { it.latestMetadata != null }.size + LocalCache.users.filter { _, it -> it.latestMetadata != null }.size +
" / " + " / " +
LocalCache.userListCache.size, LocalCache.users.size(),
) )
Log.d( Log.d(
"STATE DUMP", "STATE DUMP",
"Memory used by Events: " + "Memory used by Events: " +
LocalCache.noteListCache.sumOf { it.event?.countMemory() ?: 0 } / (1024 * 1024) + LocalCache.notes.sumOfLong { _, note -> note.event?.countMemory() ?: 0L } / (1024 * 1024) +
" MB", " MB",
) )
LocalCache.noteListCache LocalCache.notes
.groupBy { it.event?.kind() } .countByGroup { _, it -> it.event?.kind() }
.forEach { Log.d("STATE DUMP", "Kind ${it.key}: \t${it.value.size} elements ") } .forEach { Log.d("STATE DUMP", "Kind ${it.key}: \t${it.value} elements ") }
LocalCache.addressables.values LocalCache.addressables
.groupBy { it.event?.kind() } .countByGroup { _, it -> it.event?.kind() }
.forEach { Log.d("STATE DUMP", "Kind ${it.key}: \t${it.value.size} elements ") } .forEach { Log.d("STATE DUMP", "Kind ${it.key}: \t${it.value} elements ") }
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -38,7 +38,6 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter
import com.vitorpamplona.amethyst.ui.dal.DiscoverLiveNowFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
import com.vitorpamplona.amethyst.ui.theme.Size20dp import com.vitorpamplona.amethyst.ui.theme.Size20dp
@@ -46,7 +45,6 @@ import com.vitorpamplona.amethyst.ui.theme.Size23dp
import com.vitorpamplona.amethyst.ui.theme.Size24dp import com.vitorpamplona.amethyst.ui.theme.Size24dp
import com.vitorpamplona.amethyst.ui.theme.Size25dp import com.vitorpamplona.amethyst.ui.theme.Size25dp
import com.vitorpamplona.quartz.events.ChatroomKeyable import com.vitorpamplona.quartz.events.ChatroomKeyable
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@@ -309,30 +307,6 @@ object HomeLatestItem : LatestItem() {
} }
} }
object DiscoverLatestItem : LatestItem() {
fun hasNewItems(
account: Account,
newNotes: Set<Note>,
): Boolean {
checkNotInMainThread()
val lastTime = account.loadLastRead(Route.Discover.base + "Live")
val newestItem = updateNewestItem(newNotes, account, DiscoverLiveNowFeedFilter(account))
val noteEvent = newestItem?.event
val dateToUse =
if (noteEvent is LiveActivitiesEvent) {
noteEvent.starts() ?: newestItem.createdAt()
} else {
newestItem?.createdAt()
}
return (dateToUse ?: 0) > lastTime
}
}
object NotificationLatestItem : LatestItem() { object NotificationLatestItem : LatestItem() {
fun hasNewItems( fun hasNewItems(
account: Account, account: Account,
@@ -232,7 +232,7 @@ class UserReactionsViewModel(val account: Account) : ViewModel() {
val replies = mutableMapOf<String, Int>() val replies = mutableMapOf<String, Int>()
val takenIntoAccount = mutableSetOf<HexKey>() val takenIntoAccount = mutableSetOf<HexKey>()
LocalCache.noteListCache.forEach { LocalCache.notes.forEach { _, it ->
val noteEvent = it.event val noteEvent = it.event
if (noteEvent != null && !takenIntoAccount.contains(noteEvent.id())) { if (noteEvent != null && !takenIntoAccount.contains(noteEvent.id())) {
if (noteEvent is ReactionEvent) { if (noteEvent is ReactionEvent) {
@@ -59,7 +59,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
@@ -24,10 +24,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import com.vitorpamplona.amethyst.commons.BaseMediaContent import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
import com.vitorpamplona.amethyst.commons.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.MediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
@@ -27,9 +27,9 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import com.vitorpamplona.amethyst.commons.BaseMediaContent import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
import com.vitorpamplona.amethyst.commons.MediaLocalImage import com.vitorpamplona.amethyst.commons.richtext.MediaLocalImage
import com.vitorpamplona.amethyst.commons.MediaLocalVideo import com.vitorpamplona.amethyst.commons.richtext.MediaLocalVideo
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
@@ -43,10 +43,10 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.BaseMediaContent import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
import com.vitorpamplona.amethyst.commons.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.MediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
@@ -911,7 +911,7 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
} }
fun getAddressableNoteIfExists(key: String): AddressableNote? { fun getAddressableNoteIfExists(key: String): AddressableNote? {
return LocalCache.addressables[key] return LocalCache.getAddressableNoteIfExists(key)
} }
suspend fun findStatusesForUser( suspend fun findStatusesForUser(
@@ -101,7 +101,7 @@ import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map import androidx.lifecycle.map
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.MediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
@@ -674,24 +674,21 @@ fun ShowVideoStreaming(
streamingInfo?.let { event -> streamingInfo?.let { event ->
val url = remember(streamingInfo) { event.streaming() } val url = remember(streamingInfo) { event.streaming() }
val artworkUri = remember(streamingInfo) { event.image() }
val title = remember(streamingInfo) { baseChannel.toBestDisplayName() }
val author = remember(streamingInfo) { baseChannel.creatorName() }
url?.let { url?.let {
CrossfadeCheckIfUrlIsOnline(url, accountViewModel) { CrossfadeCheckIfUrlIsOnline(url, accountViewModel) {
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = remember { Modifier.heightIn(max = 300.dp) }, horizontalArrangement = Arrangement.Center,
modifier = remember { Modifier.fillMaxWidth().heightIn(min = 50.dp, max = 300.dp) },
) { ) {
val zoomableUrlVideo = val zoomableUrlVideo =
remember(it) { remember(streamingInfo) {
MediaUrlVideo( MediaUrlVideo(
url = url, url = url,
description = title, description = baseChannel.toBestDisplayName(),
artworkUri = artworkUri, artworkUri = event.image(),
authorName = author, authorName = baseChannel.creatorName(),
uri = event.toNostrUri(), uri = event.toNostrUri(),
) )
} }
@@ -112,7 +112,7 @@ import androidx.lifecycle.map
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
+4
View File
@@ -440,6 +440,8 @@
<string name="connectivity_type_always">Mindig</string> <string name="connectivity_type_always">Mindig</string>
<string name="connectivity_type_wifi_only">Csak WIFI</string> <string name="connectivity_type_wifi_only">Csak WIFI</string>
<string name="connectivity_type_never">Soha</string> <string name="connectivity_type_never">Soha</string>
<string name="ui_feature_set_type_complete">Teljes</string>
<string name="ui_feature_set_type_simplified">Egyszerűsített</string>
<string name="system">Rendszer</string> <string name="system">Rendszer</string>
<string name="light">Világos</string> <string name="light">Világos</string>
<string name="dark">Sötét</string> <string name="dark">Sötét</string>
@@ -451,6 +453,8 @@
<string name="automatically_show_url_preview">Az URL előnézetének automatikus megjelenítése</string> <string name="automatically_show_url_preview">Az URL előnézetének automatikus megjelenítése</string>
<string name="automatically_hide_nav_bars">Magával ragadó görgetés</string> <string name="automatically_hide_nav_bars">Magával ragadó görgetés</string>
<string name="automatically_hide_nav_bars_description">Navigációs sáv görgetés közbeni elrejtése</string> <string name="automatically_hide_nav_bars_description">Navigációs sáv görgetés közbeni elrejtése</string>
<string name="ui_style">Felület típusa</string>
<string name="ui_style_description">Válaszd ki a bejegyzés stílusát</string>
<string name="load_image">Kép Betöltése</string> <string name="load_image">Kép Betöltése</string>
<string name="spamming_users">Spammerek</string> <string name="spamming_users">Spammerek</string>
<string name="muted_button">Lenémítva. Kattínts a feloldásért</string> <string name="muted_button">Lenémítva. Kattínts a feloldásért</string>
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.benchmark
import androidx.benchmark.junit4.BenchmarkRule import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.commons.ExpandableTextCutOffCalculator import com.vitorpamplona.amethyst.commons.richtext.ExpandableTextCutOffCalculator
import com.vitorpamplona.amethyst.commons.nthIndexOf import com.vitorpamplona.amethyst.commons.richtext.nthIndexOf
import junit.framework.TestCase import junit.framework.TestCase
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@@ -25,10 +25,10 @@ import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.linkedin.urls.detection.UrlDetector import com.linkedin.urls.detection.UrlDetector
import com.linkedin.urls.detection.UrlDetectorOptions import com.linkedin.urls.detection.UrlDetectorOptions
import com.vitorpamplona.amethyst.commons.HashTagSegment import com.vitorpamplona.amethyst.commons.richtext.HashTagSegment
import com.vitorpamplona.amethyst.commons.ImageSegment import com.vitorpamplona.amethyst.commons.richtext.ImageSegment
import com.vitorpamplona.amethyst.commons.LinkSegment import com.vitorpamplona.amethyst.commons.richtext.LinkSegment
import com.vitorpamplona.amethyst.commons.RichTextParser import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.quartz.events.EmptyTagList import com.vitorpamplona.quartz.events.EmptyTagList
import com.vitorpamplona.quartz.events.ImmutableListOfLists import com.vitorpamplona.quartz.events.ImmutableListOfLists
import junit.framework.TestCase.assertNull import junit.framework.TestCase.assertNull
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.benchmark
import androidx.benchmark.junit4.BenchmarkRule import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.commons.Robohash import com.vitorpamplona.amethyst.commons.robohash.Robohash
import com.vitorpamplona.amethyst.commons.RobohashAssembler import com.vitorpamplona.amethyst.commons.robohash.RobohashAssembler
import okio.Buffer import okio.Buffer
import okio.buffer import okio.buffer
import okio.source import okio.source
@@ -25,58 +25,58 @@ import androidx.benchmark.junit4.measureRepeated
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.parts.accessory0Seven
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.parts.accessory1Nose
import com.vitorpamplona.amethyst.commons.robohashparts.accessory0Seven import com.vitorpamplona.amethyst.commons.parts.accessory2HornRed
import com.vitorpamplona.amethyst.commons.robohashparts.accessory1Nose import com.vitorpamplona.amethyst.commons.parts.accessory3Button
import com.vitorpamplona.amethyst.commons.robohashparts.accessory2HornRed import com.vitorpamplona.amethyst.commons.parts.accessory4Satellite
import com.vitorpamplona.amethyst.commons.robohashparts.accessory3Button import com.vitorpamplona.amethyst.commons.parts.accessory5Mustache
import com.vitorpamplona.amethyst.commons.robohashparts.accessory4Satellite import com.vitorpamplona.amethyst.commons.parts.accessory6Hat
import com.vitorpamplona.amethyst.commons.robohashparts.accessory5Mustache import com.vitorpamplona.amethyst.commons.parts.accessory7Antenna
import com.vitorpamplona.amethyst.commons.robohashparts.accessory6Hat import com.vitorpamplona.amethyst.commons.parts.accessory8Brush
import com.vitorpamplona.amethyst.commons.robohashparts.accessory7Antenna import com.vitorpamplona.amethyst.commons.parts.accessory9Horn
import com.vitorpamplona.amethyst.commons.robohashparts.accessory8Brush import com.vitorpamplona.amethyst.commons.parts.body0Trooper
import com.vitorpamplona.amethyst.commons.robohashparts.accessory9Horn import com.vitorpamplona.amethyst.commons.parts.body1Thin
import com.vitorpamplona.amethyst.commons.robohashparts.body0Trooper import com.vitorpamplona.amethyst.commons.parts.body2Thinnest
import com.vitorpamplona.amethyst.commons.robohashparts.body1Thin import com.vitorpamplona.amethyst.commons.parts.body3Front
import com.vitorpamplona.amethyst.commons.robohashparts.body2Thinnest import com.vitorpamplona.amethyst.commons.parts.body4Round
import com.vitorpamplona.amethyst.commons.robohashparts.body3Front import com.vitorpamplona.amethyst.commons.parts.body5Neck
import com.vitorpamplona.amethyst.commons.robohashparts.body4Round import com.vitorpamplona.amethyst.commons.parts.body6IronMan
import com.vitorpamplona.amethyst.commons.robohashparts.body5Neck import com.vitorpamplona.amethyst.commons.parts.body7NeckThinner
import com.vitorpamplona.amethyst.commons.robohashparts.body6IronMan import com.vitorpamplona.amethyst.commons.parts.body8Big
import com.vitorpamplona.amethyst.commons.robohashparts.body7NeckThinner import com.vitorpamplona.amethyst.commons.parts.body9Huge
import com.vitorpamplona.amethyst.commons.robohashparts.body8Big import com.vitorpamplona.amethyst.commons.parts.eyes0Squint
import com.vitorpamplona.amethyst.commons.robohashparts.body9Huge import com.vitorpamplona.amethyst.commons.parts.eyes1Round
import com.vitorpamplona.amethyst.commons.robohashparts.eyes0Squint import com.vitorpamplona.amethyst.commons.parts.eyes2Single
import com.vitorpamplona.amethyst.commons.robohashparts.eyes1Round import com.vitorpamplona.amethyst.commons.parts.eyes3Scott
import com.vitorpamplona.amethyst.commons.robohashparts.eyes2Single import com.vitorpamplona.amethyst.commons.parts.eyes4RoundSingle
import com.vitorpamplona.amethyst.commons.robohashparts.eyes3Scott import com.vitorpamplona.amethyst.commons.parts.eyes5RoundSmall
import com.vitorpamplona.amethyst.commons.robohashparts.eyes4RoundSingle import com.vitorpamplona.amethyst.commons.parts.eyes6WallE
import com.vitorpamplona.amethyst.commons.robohashparts.eyes5RoundSmall import com.vitorpamplona.amethyst.commons.parts.eyes7Bar
import com.vitorpamplona.amethyst.commons.robohashparts.eyes6WallE import com.vitorpamplona.amethyst.commons.parts.eyes8SmallBar
import com.vitorpamplona.amethyst.commons.robohashparts.eyes7Bar import com.vitorpamplona.amethyst.commons.parts.eyes9Shield
import com.vitorpamplona.amethyst.commons.robohashparts.eyes8SmallBar import com.vitorpamplona.amethyst.commons.parts.face0C3po
import com.vitorpamplona.amethyst.commons.robohashparts.eyes9Shield import com.vitorpamplona.amethyst.commons.parts.face1Rock
import com.vitorpamplona.amethyst.commons.robohashparts.face0C3po import com.vitorpamplona.amethyst.commons.parts.face2Long
import com.vitorpamplona.amethyst.commons.robohashparts.face1Rock import com.vitorpamplona.amethyst.commons.parts.face3Oval
import com.vitorpamplona.amethyst.commons.robohashparts.face2Long import com.vitorpamplona.amethyst.commons.parts.face4Cylinder
import com.vitorpamplona.amethyst.commons.robohashparts.face3Oval import com.vitorpamplona.amethyst.commons.parts.face5Baloon
import com.vitorpamplona.amethyst.commons.robohashparts.face4Cylinder import com.vitorpamplona.amethyst.commons.parts.face6Triangle
import com.vitorpamplona.amethyst.commons.robohashparts.face5Baloon import com.vitorpamplona.amethyst.commons.parts.face7Bent
import com.vitorpamplona.amethyst.commons.robohashparts.face6Triangle import com.vitorpamplona.amethyst.commons.parts.face8TriangleInv
import com.vitorpamplona.amethyst.commons.robohashparts.face7Bent import com.vitorpamplona.amethyst.commons.parts.face9Square
import com.vitorpamplona.amethyst.commons.robohashparts.face8TriangleInv import com.vitorpamplona.amethyst.commons.parts.mouth0Horz
import com.vitorpamplona.amethyst.commons.robohashparts.face9Square import com.vitorpamplona.amethyst.commons.parts.mouth1Cylinder
import com.vitorpamplona.amethyst.commons.robohashparts.mouth0Horz import com.vitorpamplona.amethyst.commons.parts.mouth2Teeth
import com.vitorpamplona.amethyst.commons.robohashparts.mouth1Cylinder import com.vitorpamplona.amethyst.commons.parts.mouth3Grid
import com.vitorpamplona.amethyst.commons.robohashparts.mouth2Teeth import com.vitorpamplona.amethyst.commons.parts.mouth4Vert
import com.vitorpamplona.amethyst.commons.robohashparts.mouth3Grid import com.vitorpamplona.amethyst.commons.parts.mouth5MidOpen
import com.vitorpamplona.amethyst.commons.robohashparts.mouth4Vert import com.vitorpamplona.amethyst.commons.parts.mouth6Cell
import com.vitorpamplona.amethyst.commons.robohashparts.mouth5MidOpen import com.vitorpamplona.amethyst.commons.parts.mouth7Happy
import com.vitorpamplona.amethyst.commons.robohashparts.mouth6Cell import com.vitorpamplona.amethyst.commons.parts.mouth8Buttons
import com.vitorpamplona.amethyst.commons.robohashparts.mouth7Happy import com.vitorpamplona.amethyst.commons.parts.mouth9Closed
import com.vitorpamplona.amethyst.commons.robohashparts.mouth8Buttons import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohashparts.mouth9Closed import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons package com.vitorpamplona.amethyst.commons.compose
import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons package com.vitorpamplona.amethyst.commons.richtext
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertEquals
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons package com.vitorpamplona.amethyst.commons.richtext
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.events.EmptyTagList import com.vitorpamplona.quartz.events.EmptyTagList
@@ -687,7 +687,7 @@ class RichTextParserTest {
@Test @Test
fun testTextToParse() { fun testTextToParse() {
val state = val state =
com.vitorpamplona.amethyst.commons.RichTextParser() RichTextParser()
.parseText(textToParse, EmptyTagList) .parseText(textToParse, EmptyTagList)
org.junit.Assert.assertEquals( org.junit.Assert.assertEquals(
"relay.shitforce.one, relayable.org, universe.nostrich.land, nos.lol, universe.nostrich.land?lang=zh, universe.nostrich.land?lang=en, relay.damus.io, relay.nostr.wirednet.jp, offchain.pub, nostr.rocks, relay.wellorder.net, nostr.oxtr.dev, universe.nostrich.land?lang=ja, relay.mostr.pub, nostr.bitcoiner.social, Nostr-Check.com, MR.Rabbit, Ancap.su, zapper.lol, smies.me, baller.hodl", "relay.shitforce.one, relayable.org, universe.nostrich.land, nos.lol, universe.nostrich.land?lang=zh, universe.nostrich.land?lang=en, relay.damus.io, relay.nostr.wirednet.jp, offchain.pub, nostr.rocks, relay.wellorder.net, nostr.oxtr.dev, universe.nostrich.land?lang=ja, relay.mostr.pub, nostr.bitcoiner.social, Nostr-Check.com, MR.Rabbit, Ancap.su, zapper.lol, smies.me, baller.hodl",
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons package com.vitorpamplona.amethyst.commons.compose
import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
@@ -0,0 +1,331 @@
/**
* 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.commons.data
import java.util.concurrent.ConcurrentSkipListMap
import java.util.function.BiConsumer
class LargeCache<K, V> {
val cache = ConcurrentSkipListMap<K, V>()
fun get(key: K) = cache.get(key)
fun remove(key: K) = cache.remove(key)
fun size() = cache.size
fun getOrCreate(
key: K,
builder: (key: K) -> V,
): V {
val value = cache.get(key)
return if (value != null) {
value
} else {
val newObject = builder(key)
cache.putIfAbsent(key, newObject) ?: newObject
}
}
fun forEach(consumer: BiConsumer<K, V>) {
innerForEach(consumer)
}
fun filter(consumer: BiFilter<K, V>): List<V> {
val runner = BiFilterCollector(consumer)
innerForEach(runner)
return runner.results
}
fun filterIntoSet(consumer: BiFilter<K, V>): Set<V> {
val runner = BiFilterUniqueCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> mapNotNull(consumer: BiMapper<K, V, R?>): List<R> {
val runner = BiMapCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> mapNotNullIntoSet(consumer: BiMapper<K, V, R?>): Set<R> {
val runner = BiMapUniqueCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> mapFlatten(consumer: BiMapper<K, V, Collection<R>?>): List<R> {
val runner = BiMapFlattenCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> mapFlattenIntoSet(consumer: BiMapper<K, V, Collection<R>?>): Set<R> {
val runner = BiMapFlattenUniqueCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> map(consumer: BiNotNullMapper<K, V, R>): List<R> {
val runner = BiNotNullMapCollector(consumer)
innerForEach(runner)
return runner.results
}
fun sumOf(consumer: BiSumOf<K, V>): Int {
val runner = BiSumOfCollector(consumer)
innerForEach(runner)
return runner.sum
}
fun sumOfLong(consumer: BiSumOfLong<K, V>): Long {
val runner = BiSumOfLongCollector(consumer)
innerForEach(runner)
return runner.sum
}
fun <R> groupBy(consumer: BiNotNullMapper<K, V, R>): Map<R, List<V>> {
val runner = BiGroupByCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> countByGroup(consumer: BiNotNullMapper<K, V, R>): Map<R, Int> {
val runner = BiCountByGroupCollector(consumer)
innerForEach(runner)
return runner.results
}
fun count(consumer: BiFilter<K, V>): Int {
val runner = BiCountIfCollector(consumer)
innerForEach(runner)
return runner.count
}
private fun innerForEach(runner: BiConsumer<K, V>) {
// val (value, elapsed) =
// measureTimedValue {
cache.forEach(runner)
// }
// println("LargeCache full loop $elapsed \t for $runner")
}
}
fun interface BiFilter<K, V> {
fun filter(
k: K,
v: V,
): Boolean
}
class BiFilterCollector<K, V>(val filter: BiFilter<K, V>) : BiConsumer<K, V> {
var results: ArrayList<V> = ArrayList()
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) {
results.add(v)
}
}
}
class BiFilterUniqueCollector<K, V>(val filter: BiFilter<K, V>) : BiConsumer<K, V> {
var results: HashSet<V> = HashSet()
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) {
results.add(v)
}
}
}
fun interface BiMapper<K, V, R> {
fun map(
k: K,
v: V,
): R?
}
class BiMapCollector<K, V, R>(val mapper: BiMapper<K, V, R?>) : BiConsumer<K, V> {
var results: ArrayList<R> = ArrayList()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.add(result)
}
}
}
class BiMapUniqueCollector<K, V, R>(val mapper: BiMapper<K, V, R?>) : BiConsumer<K, V> {
var results: HashSet<R> = HashSet()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.add(result)
}
}
}
class BiMapFlattenCollector<K, V, R>(val mapper: BiMapper<K, V, Collection<R>?>) : BiConsumer<K, V> {
var results: ArrayList<R> = ArrayList()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.addAll(result)
}
}
}
class BiMapFlattenUniqueCollector<K, V, R>(val mapper: BiMapper<K, V, Collection<R>?>) : BiConsumer<K, V> {
var results: HashSet<R> = HashSet()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.addAll(result)
}
}
}
fun interface BiNotNullMapper<K, V, R> {
fun map(
k: K,
v: V,
): R
}
class BiNotNullMapCollector<K, V, R>(val mapper: BiNotNullMapper<K, V, R>) : BiConsumer<K, V> {
var results: ArrayList<R> = ArrayList()
override fun accept(
k: K,
v: V,
) {
results.add(mapper.map(k, v))
}
}
fun interface BiSumOf<K, V> {
fun map(
k: K,
v: V,
): Int
}
class BiSumOfCollector<K, V>(val mapper: BiSumOf<K, V>) : BiConsumer<K, V> {
var sum = 0
override fun accept(
k: K,
v: V,
) {
sum += mapper.map(k, v)
}
}
fun interface BiSumOfLong<K, V> {
fun map(
k: K,
v: V,
): Long
}
class BiSumOfLongCollector<K, V>(val mapper: BiSumOfLong<K, V>) : BiConsumer<K, V> {
var sum = 0L
override fun accept(
k: K,
v: V,
) {
sum += mapper.map(k, v)
}
}
class BiGroupByCollector<K, V, R>(val mapper: BiNotNullMapper<K, V, R>) : BiConsumer<K, V> {
var results = HashMap<R, ArrayList<V>>()
override fun accept(
k: K,
v: V,
) {
val group = mapper.map(k, v)
val list = results[group]
if (list == null) {
val answer = ArrayList<V>()
answer.add(v)
results[group] = answer
} else {
list.add(v)
}
}
}
class BiCountByGroupCollector<K, V, R>(val mapper: BiNotNullMapper<K, V, R>) : BiConsumer<K, V> {
var results = HashMap<R, Int>()
override fun accept(
k: K,
v: V,
) {
val group = mapper.map(k, v)
val count = results[group]
if (count == null) {
results[group] = 1
} else {
results[group] = count + 1
}
}
}
class BiCountIfCollector<K, V>(val filter: BiFilter<K, V>) : BiConsumer<K, V> {
var count = 0
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) count++
}
}
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons package com.vitorpamplona.amethyst.commons.richtext
class ExpandableTextCutOffCalculator { class ExpandableTextCutOffCalculator {
companion object { companion object {
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons package com.vitorpamplona.amethyst.commons.richtext
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import java.io.File import java.io.File
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons package com.vitorpamplona.amethyst.commons.richtext
import android.util.Log import android.util.Log
import android.util.Patterns import android.util.Patterns
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons package com.vitorpamplona.amethyst.commons.richtext
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons package com.vitorpamplona.amethyst.commons.robohash
import android.util.LruCache import android.util.LruCache
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons package com.vitorpamplona.amethyst.commons.robohash
import android.util.Log import android.util.Log
import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.crypto.CryptoUtils
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons package com.vitorpamplona.amethyst.commons.robohash
import android.util.Log import android.util.Log
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
@@ -35,56 +35,56 @@ import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.robohashparts.accessory0Seven import com.vitorpamplona.amethyst.commons.robohash.parts.accessory0Seven
import com.vitorpamplona.amethyst.commons.robohashparts.accessory1Nose import com.vitorpamplona.amethyst.commons.robohash.parts.accessory1Nose
import com.vitorpamplona.amethyst.commons.robohashparts.accessory2HornRed import com.vitorpamplona.amethyst.commons.robohash.parts.accessory2HornRed
import com.vitorpamplona.amethyst.commons.robohashparts.accessory3Button import com.vitorpamplona.amethyst.commons.robohash.parts.accessory3Button
import com.vitorpamplona.amethyst.commons.robohashparts.accessory4Satellite import com.vitorpamplona.amethyst.commons.robohash.parts.accessory4Satellite
import com.vitorpamplona.amethyst.commons.robohashparts.accessory5Mustache import com.vitorpamplona.amethyst.commons.robohash.parts.accessory5Mustache
import com.vitorpamplona.amethyst.commons.robohashparts.accessory6Hat import com.vitorpamplona.amethyst.commons.robohash.parts.accessory6Hat
import com.vitorpamplona.amethyst.commons.robohashparts.accessory7Antenna import com.vitorpamplona.amethyst.commons.robohash.parts.accessory7Antenna
import com.vitorpamplona.amethyst.commons.robohashparts.accessory8Brush import com.vitorpamplona.amethyst.commons.robohash.parts.accessory8Brush
import com.vitorpamplona.amethyst.commons.robohashparts.accessory9Horn import com.vitorpamplona.amethyst.commons.robohash.parts.accessory9Horn
import com.vitorpamplona.amethyst.commons.robohashparts.body0Trooper import com.vitorpamplona.amethyst.commons.robohash.parts.body0Trooper
import com.vitorpamplona.amethyst.commons.robohashparts.body1Thin import com.vitorpamplona.amethyst.commons.robohash.parts.body1Thin
import com.vitorpamplona.amethyst.commons.robohashparts.body2Thinnest import com.vitorpamplona.amethyst.commons.robohash.parts.body2Thinnest
import com.vitorpamplona.amethyst.commons.robohashparts.body3Front import com.vitorpamplona.amethyst.commons.robohash.parts.body3Front
import com.vitorpamplona.amethyst.commons.robohashparts.body4Round import com.vitorpamplona.amethyst.commons.robohash.parts.body4Round
import com.vitorpamplona.amethyst.commons.robohashparts.body5Neck import com.vitorpamplona.amethyst.commons.robohash.parts.body5Neck
import com.vitorpamplona.amethyst.commons.robohashparts.body6IronMan import com.vitorpamplona.amethyst.commons.robohash.parts.body6IronMan
import com.vitorpamplona.amethyst.commons.robohashparts.body7NeckThinner import com.vitorpamplona.amethyst.commons.robohash.parts.body7NeckThinner
import com.vitorpamplona.amethyst.commons.robohashparts.body8Big import com.vitorpamplona.amethyst.commons.robohash.parts.body8Big
import com.vitorpamplona.amethyst.commons.robohashparts.body9Huge import com.vitorpamplona.amethyst.commons.robohash.parts.body9Huge
import com.vitorpamplona.amethyst.commons.robohashparts.eyes0Squint import com.vitorpamplona.amethyst.commons.robohash.parts.eyes0Squint
import com.vitorpamplona.amethyst.commons.robohashparts.eyes1Round import com.vitorpamplona.amethyst.commons.robohash.parts.eyes1Round
import com.vitorpamplona.amethyst.commons.robohashparts.eyes2Single import com.vitorpamplona.amethyst.commons.robohash.parts.eyes2Single
import com.vitorpamplona.amethyst.commons.robohashparts.eyes3Scott import com.vitorpamplona.amethyst.commons.robohash.parts.eyes3Scott
import com.vitorpamplona.amethyst.commons.robohashparts.eyes4RoundSingle import com.vitorpamplona.amethyst.commons.robohash.parts.eyes4RoundSingle
import com.vitorpamplona.amethyst.commons.robohashparts.eyes5RoundSmall import com.vitorpamplona.amethyst.commons.robohash.parts.eyes5RoundSmall
import com.vitorpamplona.amethyst.commons.robohashparts.eyes6WallE import com.vitorpamplona.amethyst.commons.robohash.parts.eyes6WallE
import com.vitorpamplona.amethyst.commons.robohashparts.eyes7Bar import com.vitorpamplona.amethyst.commons.robohash.parts.eyes7Bar
import com.vitorpamplona.amethyst.commons.robohashparts.eyes8SmallBar import com.vitorpamplona.amethyst.commons.robohash.parts.eyes8SmallBar
import com.vitorpamplona.amethyst.commons.robohashparts.eyes9Shield import com.vitorpamplona.amethyst.commons.robohash.parts.eyes9Shield
import com.vitorpamplona.amethyst.commons.robohashparts.face0C3po import com.vitorpamplona.amethyst.commons.robohash.parts.face0C3po
import com.vitorpamplona.amethyst.commons.robohashparts.face1Rock import com.vitorpamplona.amethyst.commons.robohash.parts.face1Rock
import com.vitorpamplona.amethyst.commons.robohashparts.face2Long import com.vitorpamplona.amethyst.commons.robohash.parts.face2Long
import com.vitorpamplona.amethyst.commons.robohashparts.face3Oval import com.vitorpamplona.amethyst.commons.robohash.parts.face3Oval
import com.vitorpamplona.amethyst.commons.robohashparts.face4Cylinder import com.vitorpamplona.amethyst.commons.robohash.parts.face4Cylinder
import com.vitorpamplona.amethyst.commons.robohashparts.face5Baloon import com.vitorpamplona.amethyst.commons.robohash.parts.face5Baloon
import com.vitorpamplona.amethyst.commons.robohashparts.face6Triangle import com.vitorpamplona.amethyst.commons.robohash.parts.face6Triangle
import com.vitorpamplona.amethyst.commons.robohashparts.face7Bent import com.vitorpamplona.amethyst.commons.robohash.parts.face7Bent
import com.vitorpamplona.amethyst.commons.robohashparts.face8TriangleInv import com.vitorpamplona.amethyst.commons.robohash.parts.face8TriangleInv
import com.vitorpamplona.amethyst.commons.robohashparts.face9Square import com.vitorpamplona.amethyst.commons.robohash.parts.face9Square
import com.vitorpamplona.amethyst.commons.robohashparts.mouth0Horz import com.vitorpamplona.amethyst.commons.robohash.parts.mouth0Horz
import com.vitorpamplona.amethyst.commons.robohashparts.mouth1Cylinder import com.vitorpamplona.amethyst.commons.robohash.parts.mouth1Cylinder
import com.vitorpamplona.amethyst.commons.robohashparts.mouth2Teeth import com.vitorpamplona.amethyst.commons.robohash.parts.mouth2Teeth
import com.vitorpamplona.amethyst.commons.robohashparts.mouth3Grid import com.vitorpamplona.amethyst.commons.robohash.parts.mouth3Grid
import com.vitorpamplona.amethyst.commons.robohashparts.mouth4Vert import com.vitorpamplona.amethyst.commons.robohash.parts.mouth4Vert
import com.vitorpamplona.amethyst.commons.robohashparts.mouth5MidOpen import com.vitorpamplona.amethyst.commons.robohash.parts.mouth5MidOpen
import com.vitorpamplona.amethyst.commons.robohashparts.mouth6Cell import com.vitorpamplona.amethyst.commons.robohash.parts.mouth6Cell
import com.vitorpamplona.amethyst.commons.robohashparts.mouth7Happy import com.vitorpamplona.amethyst.commons.robohash.parts.mouth7Happy
import com.vitorpamplona.amethyst.commons.robohashparts.mouth8Buttons import com.vitorpamplona.amethyst.commons.robohash.parts.mouth8Buttons
import com.vitorpamplona.amethyst.commons.robohashparts.mouth9Closed import com.vitorpamplona.amethyst.commons.robohash.parts.mouth9Closed
import com.vitorpamplona.quartz.encoders.Hex import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.encoders.HexValidator import com.vitorpamplona.quartz.encoders.HexValidator
import java.security.MessageDigest import java.security.MessageDigest
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,8 +28,8 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,8 +28,8 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,13 +28,13 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Brown import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.LightBrown import com.vitorpamplona.amethyst.commons.robohash.LightBrown
import com.vitorpamplona.amethyst.commons.LightGray import com.vitorpamplona.amethyst.commons.robohash.LightGray
import com.vitorpamplona.amethyst.commons.LightRed import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.MediumGray import com.vitorpamplona.amethyst.commons.robohash.MediumGray
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.LightRed import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.LightRed import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,8 +28,8 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.LightRed import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,8 +28,8 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,8 +28,8 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.MediumGray import com.vitorpamplona.amethyst.commons.robohash.MediumGray
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Gray import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Gray import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Gray import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Gray import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Gray import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Gray import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,10 +28,10 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Gray import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.Yellow import com.vitorpamplona.amethyst.commons.robohash.Yellow
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Gray import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Gray import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Gray import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Brown import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,10 +28,10 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Brown import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.DarkYellow import com.vitorpamplona.amethyst.commons.robohash.DarkYellow
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,10 +28,10 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Brown import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.LightRed import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.LightRed import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,10 +28,10 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Brown import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.LightRed import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,10 +28,10 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Brown import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.LightRed import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,10 +28,10 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Brown import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.LightYellow import com.vitorpamplona.amethyst.commons.robohash.LightYellow
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * 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. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package com.vitorpamplona.amethyst.commons.robohashparts package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -28,9 +28,9 @@ import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.PathData import androidx.compose.ui.graphics.vector.PathData
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.Brown import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.roboBuilder import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview @Preview
@Composable @Composable

Some files were not shown because too many files have changed in this diff Show More