Merge branch 'main' into main

pull/749/head
greenart7c3 2024-03-18 07:07:32 -03:00 zatwierdzone przez GitHub
commit d682518ddb
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
127 zmienionych plików z 1448 dodań i 1034 usunięć

Wyświetl plik

@ -28,8 +28,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.HashTagSegment
import com.vitorpamplona.amethyst.commons.RegularTextSegment
import com.vitorpamplona.amethyst.commons.richtext.HashTagSegment
import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment
import com.vitorpamplona.amethyst.ui.components.HashTag
import com.vitorpamplona.amethyst.ui.components.RenderRegular
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn

Wyświetl plik

@ -24,6 +24,7 @@ import android.util.Log
import android.util.LruCache
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.commons.data.LargeCache
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.components.BundledInsert
@ -121,30 +122,16 @@ import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.concurrent.ConcurrentHashMap
import kotlin.time.measureTimedValue
object LocalCache {
val antiSpam = AntiSpamFilter()
private val users = ConcurrentHashMap<HexKey, User>(5000)
private val notes = ConcurrentHashMap<HexKey, Note>(5000)
val users = LargeCache<HexKey, User>()
val notes = LargeCache<HexKey, Note>()
val addressables = LargeCache<String, AddressableNote>()
val channels = ConcurrentHashMap<HexKey, Channel>()
val addressables = ConcurrentHashMap<String, AddressableNote>(100)
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")
}
val awaitingPaymentRequests = ConcurrentHashMap<HexKey, Pair<Note?, (LnZapPaymentResponseEvent) -> Unit>>(10)
fun checkGetOrCreateUser(key: String): User? {
// checkNotInMainThread()
@ -166,27 +153,24 @@ object LocalCache {
fun getOrCreateUser(key: HexKey): User {
// checkNotInMainThread()
require(isValidHex(key = key)) { "$key is not a valid hex" }
return users[key]
?: run {
require(isValidHex(key = key)) { "$key is not a valid hex" }
val newObject = User(key)
users.putIfAbsent(key, newObject) ?: newObject
}
return users.getOrCreate(key) {
User(it)
}
}
fun getUserIfExists(key: String): User? {
if (key.isEmpty()) return null
return users[key]
return users.get(key)
}
fun getAddressableNoteIfExists(key: String): AddressableNote? {
return addressables[key]
return addressables.get(key)
}
fun getNoteIfExists(key: String): Note? {
return addressables[key] ?: notes[key]
return addressables.get(key) ?: notes.get(key)
}
fun getChannelIfExists(key: String): Channel? {
@ -226,24 +210,21 @@ object LocalCache {
): Note {
checkNotInMainThread()
return notes.get(idHex)
?: run {
require(isValidHex(idHex)) { "$idHex is not a valid hex" }
require(isValidHex(idHex)) { "$idHex is not a valid hex" }
notes.putIfAbsent(idHex, note) ?: note
}
return notes.getOrCreate(idHex) {
note
}
}
fun getOrCreateNote(idHex: String): Note {
checkNotInMainThread()
return notes.get(idHex)
?: run {
require(isValidHex(idHex)) { "$idHex is not a valid hex" }
require(isValidHex(idHex)) { "$idHex is not a valid hex" }
val newObject = Note(idHex)
notes.putIfAbsent(idHex, newObject) ?: newObject
}
return notes.getOrCreate(idHex) {
Note(idHex)
}
}
fun checkGetOrCreateChannel(key: String): Channel? {
@ -298,11 +279,9 @@ object LocalCache {
// we can't use naddr here because naddr might include relay info and
// the preferred relay should not be part of the index.
return addressables[key.toTag()]
?: run {
val newObject = AddressableNote(key)
addressables.putIfAbsent(key.toTag(), newObject) ?: newObject
}
return addressables.getOrCreate(key.toTag()) {
AddressableNote(key)
}
}
fun getOrCreateAddressableNote(key: ATag): AddressableNote {
@ -771,9 +750,6 @@ object LocalCache {
if (version.event == null) {
version.loadEvent(event, author, emptyList())
if (version.liveSet != null) {
updateListCache()
}
version.liveSet?.innerOts?.invalidateData()
}
@ -1433,9 +1409,6 @@ object LocalCache {
checkGetOrCreateNote(it)?.let { editedNote ->
modificationCache.remove(editedNote.idHex)
// must update list of Notes to quickly update the user.
if (editedNote.liveSet != null) {
updateListCache()
}
editedNote.liveSet?.innerModifications?.invalidateData()
}
}
@ -1646,10 +1619,10 @@ object LocalCache {
}
}
return userListCache.filter {
(it.anyNameStartsWith(username)) ||
it.pubkeyHex.startsWith(username, true) ||
it.pubkeyNpub().startsWith(username, true)
return users.filter { _, user: User ->
(user.anyNameStartsWith(username)) ||
user.pubkeyHex.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 &&
it.event !is RepostEvent &&
it.event !is CommunityPostApprovalEvent &&
it.event !is ReactionEvent &&
it.event !is GiftWrapEvent &&
it.event !is SealedGossipEvent &&
it.event !is OtsEvent &&
it.event !is LnZapEvent &&
it.event !is LnZapRequestEvent
note.event !is GenericRepostEvent &&
note.event !is RepostEvent &&
note.event !is CommunityPostApprovalEvent &&
note.event !is ReactionEvent &&
note.event !is GiftWrapEvent &&
note.event !is SealedGossipEvent &&
note.event !is OtsEvent &&
note.event !is LnZapEvent &&
note.event !is LnZapRequestEvent
) &&
(
it.event?.content()?.contains(text, true)
note.event?.content()?.contains(text, true)
?: false ||
it.event?.matchTag1With(text) ?: false ||
it.idHex.startsWith(text, true) ||
it.idNote().startsWith(text, true)
note.event?.matchTag1With(text) ?: false ||
note.idHex.startsWith(text, true) ||
note.idNote().startsWith(text, true)
)
} +
addressables.values.filter {
addressables.filter { _, addressable ->
(
it.event !is GenericRepostEvent &&
it.event !is RepostEvent &&
it.event !is CommunityPostApprovalEvent &&
it.event !is ReactionEvent &&
it.event !is GiftWrapEvent &&
it.event !is LnZapEvent &&
it.event !is LnZapRequestEvent
addressable.event !is GenericRepostEvent &&
addressable.event !is RepostEvent &&
addressable.event !is CommunityPostApprovalEvent &&
addressable.event !is ReactionEvent &&
addressable.event !is GiftWrapEvent &&
addressable.event !is LnZapEvent &&
addressable.event !is LnZapRequestEvent
) &&
(
it.event?.content()?.contains(text, true)
?: false || it.event?.matchTag1With(text) ?: false || it.idHex.startsWith(text, true)
addressable.event?.content()?.contains(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> {
checkNotInMainThread()
return addressables
.filter {
val noteEvent = it.value.event
(
noteEvent is StatusEvent &&
noteEvent.pubKey == user.pubkeyHex &&
!noteEvent.isExpired() &&
noteEvent.content.isNotBlank()
)
}
.values
return addressables.filter { _, it ->
val noteEvent = it.event
(
noteEvent is StatusEvent &&
noteEvent.pubKey == user.pubkeyHex &&
!noteEvent.isExpired() &&
noteEvent.content.isNotBlank()
)
}
.sortedWith(compareBy({ it.event?.expiration() ?: it.event?.createdAt() }, { it.idHex }))
.reversed()
.toImmutableList()
@ -1742,7 +1713,7 @@ object LocalCache {
var minTime: Long? = null
val time = TimeUtils.now()
noteListCache.forEach { item ->
notes.forEach { _, item ->
val noteEvent = item.event
if ((noteEvent is OtsEvent && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time))) {
noteEvent.verifiedTime?.let { stampedTime ->
@ -1774,7 +1745,7 @@ object LocalCache {
val time = TimeUtils.now()
val newNotes =
noteListCache.filter { item ->
notes.filter { _, item ->
val noteEvent = item.event
noteEvent is TextNoteModificationEvent && noteEvent.pubKey == originalAuthor && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time)
@ -1786,11 +1757,9 @@ object LocalCache {
}
fun cleanObservers() {
noteListCache.forEach { it.clearLive() }
addressables.forEach { it.value.clearLive() }
userListCache.forEach { it.clearLive() }
notes.forEach { _, it -> it.clearLive() }
addressables.forEach { _, it -> it.clearLive() }
users.forEach { _, it -> it.clearLive() }
}
fun pruneOldAndHiddenMessages(account: Account) {
@ -1816,8 +1785,8 @@ object LocalCache {
}
}
userListCache.forEach { userPair ->
userPair.privateChatrooms.values.map {
users.forEach { _, user ->
user.privateChatrooms.values.map {
val toBeRemoved = it.pruneMessagesToTheLatestOnly()
val childrenToBeRemoved = mutableListOf<Note>()
@ -1832,7 +1801,7 @@ object LocalCache {
if (toBeRemoved.size > 1) {
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() {
val toBeRemoved =
noteListCache
.filter {
val noteEvent = it.event
if (noteEvent is AddressableEvent) {
noteEvent.createdAt() <
(addressables[noteEvent.address().toTag()]?.event?.createdAt() ?: 0)
} else {
false
}
notes.filter { _, note ->
val noteEvent = note.event
if (noteEvent is AddressableEvent) {
noteEvent.createdAt() <
(addressables.get(noteEvent.address().toTag())?.event?.createdAt() ?: 0)
} else {
false
}
}
val childrenToBeRemoved = mutableListOf<Note>()
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) {
it.moveAllReferencesTo(newerVersion)
}
@ -1875,23 +1843,22 @@ object LocalCache {
checkNotInMainThread()
val toBeRemoved =
noteListCache
.filter {
(
(it.event is TextNoteEvent && !it.isNewThread()) ||
it.event is ReactionEvent ||
it.event is LnZapEvent ||
it.event is LnZapRequestEvent ||
it.event is ReportEvent ||
it.event is GenericRepostEvent
) &&
it.replyTo?.any { it.liveSet?.isInUse() == true } != true &&
it.liveSet?.isInUse() != true && // don't delete if observing.
it.author?.pubkeyHex !in
accounts && // don't delete if it is the logged in account
it.event?.isTaggedUsers(accounts) !=
true // don't delete if it's a notification to the logged in user
}
notes.filter { _, note ->
(
(note.event is TextNoteEvent && !note.isNewThread()) ||
note.event is ReactionEvent ||
note.event is LnZapEvent ||
note.event is LnZapRequestEvent ||
note.event is ReportEvent ||
note.event is GenericRepostEvent
) &&
note.replyTo?.any { it.liveSet?.isInUse() == true } != true &&
note.liveSet?.isInUse() != true && // don't delete if observing.
note.author?.pubkeyHex !in
accounts && // don't delete if it is the logged in account
note.event?.isTaggedUsers(accounts) !=
true // don't delete if it's a notification to the logged in user
}
val childrenToBeRemoved = mutableListOf<Note>()
@ -1958,7 +1925,7 @@ object LocalCache {
checkNotInMainThread()
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>()
@ -1983,11 +1950,7 @@ object LocalCache {
account.liveHiddenUsers.value
?.hiddenUsers
?.map { userHex ->
(
noteListCache.filter { it.event?.pubKey() == userHex } +
addressables.values.filter { it.event?.pubKey() == userHex }
)
.toSet()
(notes.filter { _, it -> it.event?.pubKey() == userHex } + addressables.filter { _, it -> it.event?.pubKey() == userHex }).toSet()
}
?.flatten()
?: emptyList()
@ -2006,13 +1969,13 @@ object LocalCache {
checkNotInMainThread()
var removingContactList = 0
userListCache.forEach {
users.forEach { _, user ->
if (
it.pubkeyHex !in loggedIn &&
(it.liveSet == null || it.liveSet?.isInUse() == false) &&
it.latestContactList != null
user.pubkeyHex !in loggedIn &&
(user.liveSet == null || user.liveSet?.isInUse() == false) &&
user.latestContactList != null
) {
it.latestContactList = null
user.latestContactList = null
removingContactList++
}
}
@ -2172,7 +2135,6 @@ class LocalCacheLiveData {
fun invalidateData(newNote: Note) {
bundler.invalidateList(newNote) {
bundledNewNotes ->
LocalCache.updateListCache()
_newEventBundles.emit(bundledNewNotes)
}
}

Wyświetl plik

@ -354,7 +354,7 @@ class User(val pubkeyHex: String) {
}
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> {
@ -378,7 +378,7 @@ class User(val pubkeyHex: String) {
}
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 {

Wyświetl plik

@ -21,8 +21,8 @@
package com.vitorpamplona.amethyst.service
import android.util.LruCache
import com.vitorpamplona.amethyst.commons.RichTextParser
import com.vitorpamplona.amethyst.commons.RichTextViewerState
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
import com.vitorpamplona.quartz.events.ImmutableListOfLists
object CachedRichTextParser {

Wyświetl plik

@ -40,6 +40,8 @@ object NostrVideoDataSource : NostrDataSource("VideoFeed") {
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() {
job?.cancel()
job =
@ -68,6 +70,7 @@ object NostrVideoDataSource : NostrDataSource("VideoFeed") {
authors = follows,
kinds = listOf(FileHeaderEvent.KIND, FileStorageHeaderEvent.KIND),
limit = 200,
tags = mapOf("m" to SUPPORTED_VIDEO_MIME_TYPES),
since =
latestEOSEs.users[account.userProfile()]
?.followList
@ -93,6 +96,7 @@ object NostrVideoDataSource : NostrDataSource("VideoFeed") {
hashToLoad
.map { listOf(it, it.lowercase(), it.uppercase(), it.capitalize()) }
.flatten(),
"m" to SUPPORTED_VIDEO_MIME_TYPES,
),
limit = 100,
since =
@ -120,6 +124,7 @@ object NostrVideoDataSource : NostrDataSource("VideoFeed") {
hashToLoad
.map { listOf(it, it.lowercase(), it.uppercase(), it.capitalize()) }
.flatten(),
"m" to SUPPORTED_VIDEO_MIME_TYPES,
),
limit = 100,
since =

Wyświetl plik

@ -46,7 +46,7 @@ class MultiPlayerPlaybackManager(
private val playingMap = mutableMapOf<String, MediaSession>()
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(
evicted: Boolean,
key: String?,

Wyświetl plik

@ -98,7 +98,7 @@ object Client : RelayPool.Listener {
checkNotInMainThread()
subscriptions = subscriptions + Pair(subscriptionId, filters)
RelayPool.sendFilter(subscriptionId)
RelayPool.sendFilter(subscriptionId, filters)
}
fun sendFilterOnlyIfDisconnected(
@ -134,7 +134,7 @@ object Client : RelayPool.Listener {
newSporadicRelay(
relay,
feedTypes,
onConnected = { relay -> relay.send(signedEvent) },
onConnected = { myRelay -> myRelay.send(signedEvent) },
onDone = onDone,
)
}
@ -152,7 +152,9 @@ object Client : RelayPool.Listener {
RelayPool.addRelay(relay)
relay.connectAndRun {
allSubscriptions().forEach { relay.sendFilter(requestId = it) }
allSubscriptions().forEach {
relay.sendFilter(it.key, it.value)
}
onConnected(relay)
@ -264,8 +266,8 @@ object Client : RelayPool.Listener {
listeners = listeners.minus(listener)
}
fun allSubscriptions(): Set<String> {
return subscriptions.keys
fun allSubscriptions(): Map<String, List<TypedFilter>> {
return subscriptions
}
fun getSubscriptionFilters(subId: String): List<TypedFilter> {

Wyświetl plik

@ -344,16 +344,15 @@ class Relay(
afterEOSEPerSubscription = LinkedHashMap(afterEOSEPerSubscription.size)
}
fun sendFilter(requestId: String) {
fun sendFilter(
requestId: String,
filters: List<TypedFilter>,
) {
checkNotInMainThread()
if (read) {
if (isConnected()) {
if (isReady) {
val filters =
Client.getSubscriptionFilters(requestId).filter { filter ->
activeTypes.any { it in filter.types }
}
if (filters.isNotEmpty()) {
val request =
filters.joinToStringLimited(
@ -423,7 +422,14 @@ class Relay(
fun renewFilters() {
// 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) {

Wyświetl plik

@ -77,8 +77,11 @@ object RelayPool : Relay.Listener {
relays.forEach { it.connect() }
}
fun sendFilter(subscriptionId: String) {
relays.forEach { it.sendFilter(subscriptionId) }
fun sendFilter(
subscriptionId: String,
filters: List<TypedFilter>,
) {
relays.forEach { it.sendFilter(subscriptionId, filters) }
}
fun connectAndSendFiltersIfDisconnected() {

Wyświetl plik

@ -87,7 +87,7 @@ import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
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.service.NostrSearchEventOrUserDataSource
import com.vitorpamplona.amethyst.ui.components.BechLink

Wyświetl plik

@ -31,8 +31,8 @@ import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.commons.RichTextParser
import com.vitorpamplona.amethyst.commons.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note

Wyświetl plik

@ -125,7 +125,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
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.User
import com.vitorpamplona.amethyst.service.Nip96MediaServers

Wyświetl plik

@ -35,8 +35,8 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.fonfon.kgeohash.toGeoHash
import com.vitorpamplona.amethyst.commons.RichTextParser
import com.vitorpamplona.amethyst.commons.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note

Wyświetl plik

@ -44,7 +44,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
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.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder

Wyświetl plik

@ -27,9 +27,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import com.vitorpamplona.amethyst.commons.MediaUrlImage
import com.vitorpamplona.amethyst.commons.MediaUrlVideo
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding

Wyświetl plik

@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.components
import android.util.Log
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.service.checkNotInMainThread
import com.vitorpamplona.quartz.encoders.ATag

Wyświetl plik

@ -69,24 +69,24 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.halilibo.richtext.markdown.Markdown
import com.halilibo.richtext.markdown.MarkdownParseOptions
import com.halilibo.richtext.ui.material3.Material3RichText
import com.vitorpamplona.amethyst.commons.BechSegment
import com.vitorpamplona.amethyst.commons.CashuSegment
import com.vitorpamplona.amethyst.commons.EmailSegment
import com.vitorpamplona.amethyst.commons.EmojiSegment
import com.vitorpamplona.amethyst.commons.HashIndexEventSegment
import com.vitorpamplona.amethyst.commons.HashIndexUserSegment
import com.vitorpamplona.amethyst.commons.HashTagSegment
import com.vitorpamplona.amethyst.commons.ImageSegment
import com.vitorpamplona.amethyst.commons.InvoiceSegment
import com.vitorpamplona.amethyst.commons.LinkSegment
import com.vitorpamplona.amethyst.commons.MediaUrlImage
import com.vitorpamplona.amethyst.commons.PhoneSegment
import com.vitorpamplona.amethyst.commons.RegularTextSegment
import com.vitorpamplona.amethyst.commons.RichTextParser
import com.vitorpamplona.amethyst.commons.RichTextViewerState
import com.vitorpamplona.amethyst.commons.SchemelessUrlSegment
import com.vitorpamplona.amethyst.commons.Segment
import com.vitorpamplona.amethyst.commons.WithdrawSegment
import com.vitorpamplona.amethyst.commons.richtext.BechSegment
import com.vitorpamplona.amethyst.commons.richtext.CashuSegment
import com.vitorpamplona.amethyst.commons.richtext.EmailSegment
import com.vitorpamplona.amethyst.commons.richtext.EmojiSegment
import com.vitorpamplona.amethyst.commons.richtext.HashIndexEventSegment
import com.vitorpamplona.amethyst.commons.richtext.HashIndexUserSegment
import com.vitorpamplona.amethyst.commons.richtext.HashTagSegment
import com.vitorpamplona.amethyst.commons.richtext.ImageSegment
import com.vitorpamplona.amethyst.commons.richtext.InvoiceSegment
import com.vitorpamplona.amethyst.commons.richtext.LinkSegment
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.richtext.PhoneSegment
import com.vitorpamplona.amethyst.commons.richtext.RegularTextSegment
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.commons.richtext.RichTextViewerState
import com.vitorpamplona.amethyst.commons.richtext.SchemelessUrlSegment
import com.vitorpamplona.amethyst.commons.richtext.Segment
import com.vitorpamplona.amethyst.commons.richtext.WithdrawSegment
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.HashtagIcon
import com.vitorpamplona.amethyst.model.Note

Wyświetl plik

@ -30,7 +30,7 @@ import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.request.ImageRequest
import coil.request.Options
import com.vitorpamplona.amethyst.commons.Robohash
import com.vitorpamplona.amethyst.commons.robohash.Robohash
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import okio.buffer
import okio.source

Wyświetl plik

@ -49,7 +49,7 @@ import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.request.ImageRequest
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.ui.theme.isLight
import java.util.Base64

Wyświetl plik

@ -106,13 +106,13 @@ import coil.compose.AsyncImage
import coil.compose.AsyncImagePainter
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.BaseMediaContent
import com.vitorpamplona.amethyst.commons.MediaLocalImage
import com.vitorpamplona.amethyst.commons.MediaLocalVideo
import com.vitorpamplona.amethyst.commons.MediaPreloadedContent
import com.vitorpamplona.amethyst.commons.MediaUrlContent
import com.vitorpamplona.amethyst.commons.MediaUrlImage
import com.vitorpamplona.amethyst.commons.MediaUrlVideo
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
import com.vitorpamplona.amethyst.commons.richtext.MediaLocalImage
import com.vitorpamplona.amethyst.commons.richtext.MediaLocalVideo
import com.vitorpamplona.amethyst.commons.richtext.MediaPreloadedContent
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlContent
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
import com.vitorpamplona.amethyst.service.BlurHashRequester
import com.vitorpamplona.amethyst.ui.actions.CloseButton
import com.vitorpamplona.amethyst.ui.actions.InformationDialog

Wyświetl plik

@ -45,7 +45,6 @@ class BookmarkPrivateFeedFilter(val account: Account) : FeedFilter<Note>() {
return notes
.plus(addresses)
.toSet()
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
.reversed()
.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -40,7 +40,6 @@ class BookmarkPublicFeedFilter(val account: Account) : FeedFilter<Note>() {
return notes
.plus(addresses)
.toSet()
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
.reversed()
.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -44,6 +44,6 @@ class ChannelFeedFilter(val channel: Channel, val account: Account) : AdditiveFe
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
return collection.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -47,6 +47,6 @@ class ChatroomFeedFilter(val withUser: ChatroomKey, val account: Account) :
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
return collection.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -197,6 +197,6 @@ class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter<Not
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
return collection.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -138,6 +138,6 @@ class ChatroomListNewFeedFilter(val account: Account) : AdditiveFeedFilter<Note>
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
return collection.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -24,16 +24,22 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
class CommunityFeedFilter(val note: AddressableNote, val account: Account) :
AdditiveFeedFilter<Note>() {
class CommunityFeedFilter(val note: AddressableNote, val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + note.idHex
}
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> {
@ -41,31 +47,36 @@ class CommunityFeedFilter(val note: AddressableNote, val account: Account) :
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val myUnapprovedPosts =
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 myPubKey = account.userProfile().pubkeyHex
val approvedPosts =
collection
.asSequence()
.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 collection.mapNotNull {
filterMap(it, myPubKey)
}.flatten().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> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
return collection.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -20,36 +20,6 @@
*/
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.service.OnlineChecker
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_LIVE
class DiscoverLiveNowFeedFilter(
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()
}
}
val DefaultFeedOrder = compareBy<Note>({ it.createdAt() }, { it.idHex }).reversed()

Wyświetl plik

@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
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.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
@ -56,39 +54,34 @@ open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Not
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> {
val now = TimeUtils.now()
val isGlobal = account.defaultDiscoveryFollowList.value == GLOBAL_FOLLOWS
val isHiddenList = showHiddenKey()
val params = buildFilterParams(account)
val followingKeySet = account.liveDiscoveryFollowLists.value?.users ?: emptySet()
val followingTagSet = account.liveDiscoveryFollowLists.value?.hashtags ?: emptySet()
val followingGeohashSet = account.liveDiscoveryFollowLists.value?.geotags ?: emptySet()
val createEvents = collection.filter { it.event is ChannelCreateEvent }
val anyOtherChannelEvent =
collection
.asSequence()
.filter { it.event is IsInPublicChatChannel }
.mapNotNull { (it.event as? IsInPublicChatChannel)?.channel() }
.mapNotNull { LocalCache.checkGetOrCreateNote(it) }
.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
return collection.mapNotNullTo(HashSet()) { note ->
// note event here will never be null
val noteEvent = note.event
if (noteEvent is ChannelCreateEvent && params.match(noteEvent)) {
note
} else if (noteEvent is IsInPublicChatChannel) {
val channel = noteEvent.channel()?.let { LocalCache.checkGetOrCreateNote(it) }
if (channel != null && (channel.event == null || params.match(channel.event))) {
channel
} else {
null
}
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true }
.filter { (it.createdAt() ?: 0) <= now }
.toSet()
return activities
} else {
null
}
}
}
override fun sort(collection: Set<Note>): List<Note> {

Wyświetl plik

@ -21,15 +21,14 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
@ -44,9 +43,27 @@ open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilte
}
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)
}
@ -56,41 +73,44 @@ open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilte
}
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val now = TimeUtils.now()
val isGlobal = account.defaultDiscoveryFollowList.value == GLOBAL_FOLLOWS
val isHiddenList = showHiddenKey()
// here, we need to look for CommunityDefinition in new collection AND new CommunityDefinition from Post Approvals
val filterParams =
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()
val followingTagSet = account.liveDiscoveryFollowLists.value?.hashtags ?: emptySet()
val followingGeohashSet = account.liveDiscoveryFollowLists.value?.geotags ?: emptySet()
return collection.mapNotNull { note ->
// note event here will never be null
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 }
val anyOtherCommunityEvent =
collection
.asSequence()
.filter { it.event is CommunityPostApprovalEvent }
.mapNotNull { (it.event as? CommunityPostApprovalEvent)?.communities() }
.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
if (definitionEvent == null && shouldInclude(it, filterParams)) {
definitionNote
} else if (definitionEvent is CommunityDefinitionEvent && filterParams.match(definitionEvent)) {
definitionNote
} else {
null
}
}
.filter { isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true }
.filter { (it.createdAt() ?: 0) <= now }
.toSet()
return activities
} else {
null
}
}.flatten().toSet()
}
private fun shouldInclude(
aTag: ATag?,
params: FilterByListParams,
) = aTag != null && aTag.kind == CommunityDefinitionEvent.KIND && params.match(aTag)
override fun sort(collection: Set<Note>): List<Note> {
val followingKeySet =
account.liveDiscoveryFollowLists.value?.users ?: account.liveKind3Follows.value.users

Wyświetl plik

@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
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.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
open class DiscoverLiveFeedFilter(
val account: Account,
@ -64,33 +62,15 @@ open class DiscoverLiveFeedFilter(
}
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val now = TimeUtils.now()
val isGlobal = account.defaultDiscoveryFollowList.value == GLOBAL_FOLLOWS
val isHiddenList = showHiddenKey()
val filterParams =
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()
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
return collection.filterTo(HashSet()) { it.event is LiveActivitiesEvent && filterParams.match(it.event) }
}
override fun sort(collection: Set<Note>): List<Note> {

Wyświetl plik

@ -21,13 +21,11 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.events.ClassifiedsEvent
import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
open class DiscoverMarketplaceFeedFilter(
val account: Account,
@ -46,10 +44,13 @@ open class DiscoverMarketplaceFeedFilter(
}
override fun feed(): List<Note> {
val classifieds =
LocalCache.addressables.filter { it.value.event is ClassifiedsEvent }.map { it.value }
val params = buildFilterParams(account)
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)
}
@ -58,35 +59,22 @@ open class DiscoverMarketplaceFeedFilter(
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> {
val now = TimeUtils.now()
val isGlobal = account.defaultDiscoveryFollowList.value == GLOBAL_FOLLOWS
val isHiddenList = showHiddenKey()
val params = buildFilterParams(account)
val followingKeySet = account.liveDiscoveryFollowLists.value?.users ?: emptySet()
val followingTagSet = account.liveDiscoveryFollowLists.value?.hashtags ?: emptySet()
val followingGeohashSet = account.liveDiscoveryFollowLists.value?.geotags ?: emptySet()
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
return collection.filterTo(HashSet()) {
val noteEvent = it.event
noteEvent is ClassifiedsEvent && noteEvent.isWellFormed() && params.match(noteEvent)
}
}
override fun sort(collection: Set<Note>): List<Note> {

Wyświetl plik

@ -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,
)
}
}
}

Wyświetl plik

@ -36,7 +36,12 @@ class GeoHashFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
}
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> {
@ -44,25 +49,24 @@ class GeoHashFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val myTag = tag ?: return emptySet()
return collection.filterTo(HashSet<Note>()) { acceptableEvent(it, tag) }
}
return collection
.asSequence()
.filter {
(
it.event is TextNoteEvent ||
it.event is LongTextNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is PrivateDmEvent ||
it.event is PollNoteEvent ||
it.event is AudioHeaderEvent
) && it.event?.isTaggedGeoHash(myTag) == true
}
.filter { account.isAcceptable(it) }
.toSet()
fun acceptableEvent(
it: Note,
geoTag: String,
): Boolean {
return (
it.event is TextNoteEvent ||
it.event is LongTextNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is PrivateDmEvent ||
it.event is PollNoteEvent ||
it.event is AudioHeaderEvent
) && it.event?.isTaggedGeoHash(geoTag) == true && account.isAcceptable(it)
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
return collection.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -36,7 +36,12 @@ class HashtagFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
}
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> {
@ -44,25 +49,24 @@ class HashtagFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val myTag = tag ?: return emptySet()
return collection.filterTo(HashSet<Note>()) { acceptableEvent(it, tag) }
}
return collection
.asSequence()
.filter {
(
it.event is TextNoteEvent ||
it.event is LongTextNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is PrivateDmEvent ||
it.event is PollNoteEvent ||
it.event is AudioHeaderEvent
) && it.event?.isTaggedHash(myTag) == true
}
.filter { account.isAcceptable(it) }
.toSet()
fun acceptableEvent(
it: Note,
hashTag: String,
): Boolean {
return (
it.event is TextNoteEvent ||
it.event is LongTextNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is PrivateDmEvent ||
it.event is PollNoteEvent ||
it.event is AudioHeaderEvent
) && it.event?.isTaggedHash(hashTag) == true && account.isAcceptable(it)
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
return collection.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -21,17 +21,14 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.events.ChannelMessageEvent
import com.vitorpamplona.quartz.events.DraftEvent
import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.utils.TimeUtils
class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
@ -39,56 +36,54 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Not
}
override fun showHiddenKey(): Boolean {
return account.defaultHomeFollowList.value ==
PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultHomeFollowList.value ==
MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
return account.defaultHomeFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultHomeFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
}
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> {
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> {
val isGlobal = account.defaultHomeFollowList.value == GLOBAL_FOLLOWS
val isHiddenList = showHiddenKey()
val filterParams = buildFilterParams(account)
val followingKeySet = account.liveHomeFollowLists.value?.users ?: emptySet()
val followingTagSet = account.liveHomeFollowLists.value?.hashtags ?: emptySet()
val followingGeohashSet = account.liveHomeFollowLists.value?.geotags ?: emptySet()
return collection.filterTo(HashSet()) {
acceptableEvent(it, filterParams)
}
}
val now = TimeUtils.now()
return collection
.asSequence()
.filter {
(
it.event is TextNoteEvent ||
it.event is PollNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is LiveActivitiesChatMessageEvent ||
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()
fun acceptableEvent(
it: Note,
filterParams: FilterByListParams,
): Boolean {
return (
it.event is TextNoteEvent ||
it.event is PollNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is LiveActivitiesChatMessageEvent
) && filterParams.match(it.event) && !it.isNewThread()
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
return collection.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
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.RepostEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.utils.TimeUtils
class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
@ -43,69 +41,70 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
}
override fun showHiddenKey(): Boolean {
return account.defaultHomeFollowList.value ==
PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultHomeFollowList.value ==
MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
return account.defaultHomeFollowList.value == PeopleListEvent.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> {
val notes = innerApplyFilter(LocalCache.noteListCache, true)
val longFormNotes = innerApplyFilter(LocalCache.addressables.values, false)
val gRelays = account.activeGlobalRelays().toSet()
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)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection, false)
return innerApplyFilter(collection)
}
private fun innerApplyFilter(
collection: Collection<Note>,
ignoreAddressables: Boolean,
): Set<Note> {
val isGlobal = account.defaultHomeFollowList.value == GLOBAL_FOLLOWS
val gRelays = account.activeGlobalRelays()
val isHiddenList = showHiddenKey()
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val gRelays = account.activeGlobalRelays().toSet()
val filterParams = buildFilterParams(account)
val followingKeySet = account.liveHomeFollowLists.value?.users ?: emptySet()
val followingTagSet = account.liveHomeFollowLists.value?.hashtags ?: emptySet()
val followingGeohashSet = account.liveHomeFollowLists.value?.geotags ?: emptySet()
val followingCommunities = account.liveHomeFollowLists.value?.communities ?: emptySet()
return collection.filterTo(HashSet()) {
acceptableEvent(it, gRelays, filterParams)
}
}
val oneMinuteInTheFuture = TimeUtils.now() + (1 * 60) // one minute in the future.
return collection
.asSequence()
.filter { it ->
val noteEvent = it.event
val isGlobalRelay = it.relays.any { gRelays.contains(it.url) }
(
noteEvent is TextNoteEvent ||
noteEvent is ClassifiedsEvent ||
noteEvent is RepostEvent ||
noteEvent is GenericRepostEvent ||
noteEvent is LongTextNoteEvent ||
noteEvent is PollNoteEvent ||
noteEvent is HighlightEvent ||
noteEvent is AudioTrackEvent ||
noteEvent is AudioHeaderEvent
) &&
(!ignoreAddressables || noteEvent.kind() < 10000) &&
(
(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()
fun acceptableEvent(
it: Note,
globalRelays: Set<String>,
filterParams: FilterByListParams,
): Boolean {
val noteEvent = it.event
val isGlobalRelay = it.relays.any { globalRelays.contains(it.url) }
return (
noteEvent is TextNoteEvent ||
noteEvent is ClassifiedsEvent ||
noteEvent is RepostEvent ||
noteEvent is GenericRepostEvent ||
noteEvent is LongTextNoteEvent ||
noteEvent is PollNoteEvent ||
noteEvent is HighlightEvent ||
noteEvent is AudioTrackEvent ||
noteEvent is AudioHeaderEvent
) &&
filterParams.match(noteEvent, isGlobalRelay) &&
it.isNewThread()
}
override fun sort(collection: Set<Note>): List<Note> {
@ -115,6 +114,6 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
} else {
it.idHex
}
}.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
}.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
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.GitPatchEvent
import com.vitorpamplona.quartz.events.HighlightEvent
import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.LnZapRequestEvent
import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
@ -54,8 +52,24 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
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> {
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> {
@ -63,32 +77,31 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val isGlobal = account.defaultNotificationFollowList.value == GLOBAL_FOLLOWS
val isHiddenList = showHiddenKey()
val filterParams = buildFilterParams(account)
val followingKeySet = account.liveNotificationFollowLists.value?.users ?: emptySet()
return collection.filterTo(HashSet()) { acceptableEvent(it, filterParams) }
}
val loggedInUser = account.userProfile()
val loggedInUserHex = loggedInUser.pubkeyHex
fun acceptableEvent(
it: Note,
filterParams: FilterByListParams,
): Boolean {
val loggedInUserHex = account.userProfile().pubkeyHex
return collection
.filterTo(HashSet()) {
it.event !is ChannelCreateEvent &&
it.event !is ChannelMetadataEvent &&
it.event !is LnZapRequestEvent &&
it.event !is BadgeDefinitionEvent &&
it.event !is BadgeProfilesEvent &&
it.event !is GiftWrapEvent &&
(it.event is LnZapEvent || it.author !== loggedInUser) &&
(isGlobal || it.author?.pubkeyHex in followingKeySet) &&
it.event?.isTaggedUser(loggedInUserHex) ?: false &&
(isHiddenList || it.author == null || !account.isHidden(it.author!!.pubkeyHex)) &&
tagsAnEventByUser(it, loggedInUserHex)
}
return it.event !is ChannelCreateEvent &&
it.event !is ChannelMetadataEvent &&
it.event !is LnZapRequestEvent &&
it.event !is BadgeDefinitionEvent &&
it.event !is BadgeProfilesEvent &&
it.event !is GiftWrapEvent &&
(filterParams.isGlobal || filterParams.followLists?.users?.contains(it.author?.pubkeyHex) == true) &&
it.event?.isTaggedUser(loggedInUserHex) ?: false &&
(filterParams.isHiddenList || it.author == null || !account.isHidden(it.author!!.pubkeyHex)) &&
tagsAnEventByUser(it, loggedInUserHex)
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
return collection.sortedWith(DefaultFeedOrder)
}
fun tagsAnEventByUser(

Wyświetl plik

@ -31,7 +31,12 @@ class UserProfileAppRecommendationsFeedFilter(val user: User) : AdditiveFeedFilt
}
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> {
@ -39,26 +44,21 @@ class UserProfileAppRecommendationsFeedFilter(val user: User) : AdditiveFeedFilt
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val recommendations =
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 collection.mapNotNull { filterMap(it) }.flatten().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> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
return collection.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -47,7 +47,6 @@ class UserProfileBookmarksFeedFilter(val user: User, val account: Account) : Fee
return (notes + addresses)
.filter { account.isAcceptable(it) }
.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
.reversed()
.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -36,7 +36,17 @@ class UserProfileConversationsFeedFilter(val user: User, val account: Account) :
}
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> {
@ -44,23 +54,21 @@ class UserProfileConversationsFeedFilter(val user: User, val account: Account) :
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
return collection
.filter {
it.author == user &&
(
it.event is TextNoteEvent ||
it.event is PollNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is LiveActivitiesChatMessageEvent
) &&
!it.isNewThread() &&
account.isAcceptable(it) == true
}
.toSet()
return collection.filterTo(HashSet()) { acceptableEvent(it) }
}
fun acceptableEvent(it: Note): Boolean {
return it.author == user &&
(
it.event is TextNoteEvent ||
it.event is PollNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is LiveActivitiesChatMessageEvent
) && !it.isNewThread() && account.isAcceptable(it)
}
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

Wyświetl plik

@ -30,7 +30,9 @@ class UserProfileFollowersFeedFilter(val user: User, val account: Account) : Fee
}
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

Wyświetl plik

@ -41,8 +41,15 @@ class UserProfileNewThreadFeedFilter(val user: User, val account: Account) :
}
override fun feed(): List<Note> {
val notes = innerApplyFilter(LocalCache.noteListCache)
val longFormNotes = innerApplyFilter(LocalCache.addressables.values)
val notes =
LocalCache.notes.filterIntoSet { _, it ->
acceptableEvent(it)
}
val longFormNotes =
LocalCache.addressables.filterIntoSet { _, it ->
acceptableEvent(it)
}
return sort(notes + longFormNotes)
}
@ -52,28 +59,26 @@ class UserProfileNewThreadFeedFilter(val user: User, val account: Account) :
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
return collection
.filter {
it.author == user &&
(
it.event is TextNoteEvent ||
it.event is ClassifiedsEvent ||
it.event is RepostEvent ||
it.event is GenericRepostEvent ||
it.event is LongTextNoteEvent ||
it.event is PollNoteEvent ||
it.event is HighlightEvent ||
it.event is AudioTrackEvent ||
it.event is AudioHeaderEvent
) &&
it.isNewThread() &&
account.isAcceptable(it) == true
}
.toSet()
return collection.filterTo(HashSet()) { acceptableEvent(it) }
}
fun acceptableEvent(it: Note): Boolean {
return it.author == user &&
(
it.event is TextNoteEvent ||
it.event is ClassifiedsEvent ||
it.event is RepostEvent ||
it.event is GenericRepostEvent ||
it.event is LongTextNoteEvent ||
it.event is PollNoteEvent ||
it.event is HighlightEvent ||
it.event is AudioTrackEvent ||
it.event is AudioHeaderEvent
) && it.isNewThread() && account.isAcceptable(it)
}
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

Wyświetl plik

@ -44,7 +44,7 @@ class UserProfileReportsFeedFilter(val user: User) : AdditiveFeedFilter<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

Wyświetl plik

@ -21,14 +21,12 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.events.FileHeaderEvent
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
@ -36,14 +34,17 @@ class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
}
override fun showHiddenKey(): Boolean {
return account.defaultStoriesFollowList.value ==
PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultStoriesFollowList.value ==
MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
return account.defaultStoriesFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultStoriesFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
}
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)
}
@ -53,36 +54,32 @@ class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val now = TimeUtils.now()
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 params = buildFilterParams(account)
val followingKeySet = account.liveStoriesFollowLists.value?.users ?: emptySet()
val followingTagSet = account.liveStoriesFollowLists.value?.hashtags ?: emptySet()
val followingGeohashSet = account.liveStoriesFollowLists.value?.geotags ?: emptySet()
return collection.filterTo(HashSet()) { acceptableEvent(it, params) }
}
return collection
.asSequence()
.filter {
(it.event is FileHeaderEvent && (it.event as FileHeaderEvent).hasUrl()) ||
it.event is FileStorageHeaderEvent
}
.filter {
isGlobal ||
it.author?.pubkeyHex in followingKeySet ||
(it.event?.isTaggedHashes(followingTagSet) ?: false) ||
(it.event?.isTaggedGeoHashes(followingGeohashSet) ?: false)
}
.filter { isHiddenList || account.isAcceptable(it) }
.filter { it.createdAt()!! <= now }
.toSet()
fun acceptableEvent(
it: Note,
params: FilterByListParams,
): Boolean {
val noteEvent = it.event
return ((noteEvent is FileHeaderEvent && noteEvent.hasUrl() && noteEvent.isImageOrVideo()) || (noteEvent is FileStorageHeaderEvent && noteEvent.isImageOrVideo())) &&
params.match(noteEvent) &&
account.isAcceptable(it)
}
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
userHex = account.userProfile().pubkeyHex,
selectedListName = account.defaultStoriesFollowList.value,
followLists = account.liveStoriesFollowLists.value,
hiddenUsers = account.flowHiddenUsers.value,
)
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
return collection.sortedWith(DefaultFeedOrder)
}
}

Wyświetl plik

@ -356,7 +356,9 @@ private fun RenderRoomTopBar(
RoomNameOnlyDisplay(
room,
Modifier.padding(start = 10.dp).weight(1f),
Modifier
.padding(start = 10.dp)
.weight(1f),
fontWeight = FontWeight.Normal,
accountViewModel.userProfile(),
)
@ -504,7 +506,10 @@ fun GenericMainTopBar(
) {
Box(Modifier) {
Column(
modifier = Modifier.fillMaxWidth().fillMaxHeight(),
modifier =
Modifier
.fillMaxWidth()
.fillMaxHeight(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
@ -650,15 +655,15 @@ class FollowListViewModel(val account: Account) : ViewModel() {
val newFollowLists =
LocalCache.addressables
.mapNotNull {
val event = (it.value.event as? PeopleListEvent)
.mapNotNull { _, addressableNote ->
val event = (addressableNote.event as? PeopleListEvent)
// Has to have an list
if (
event != null &&
event.pubKey == account.userProfile().pubkeyHex &&
(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 {
null
}
@ -784,12 +789,14 @@ fun SimpleTextSpinner(
}
Box(
modifier =
Modifier.matchParentSize().clickable(
interactionSource = interactionSource,
indication = null,
) {
optionsShowing = true
},
Modifier
.matchParentSize()
.clickable(
interactionSource = interactionSource,
indication = null,
) {
optionsShowing = true
},
)
}
@ -922,13 +929,7 @@ fun AmethystClickableIcon() {
fun debugState(context: Context) {
Client.allSubscriptions()
.map {
"$it ${
Client.getSubscriptionFilters(it)
.joinToString { it.filter.toJson() }
}"
}
.forEach { Log.d("STATE DUMP", it) }
.forEach { Log.d("STATE DUMP", "${it.key} ${it.value.joinToString { it.filter.toJson() }}") }
NostrAccountDataSource.printCounter()
NostrChannelDataSource.printCounter()
@ -973,44 +974,44 @@ fun debugState(context: Context) {
Log.d(
"STATE DUMP",
"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(
"STATE DUMP",
"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(
"STATE DUMP",
"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(
"STATE DUMP",
"Memory used by Events: " +
LocalCache.noteListCache.sumOf { it.event?.countMemory() ?: 0 } / (1024 * 1024) +
LocalCache.notes.sumOfLong { _, note -> note.event?.countMemory() ?: 0L } / (1024 * 1024) +
" MB",
)
LocalCache.noteListCache
.groupBy { it.event?.kind() }
.forEach { Log.d("STATE DUMP", "Kind ${it.key}: \t${it.value.size} elements ") }
LocalCache.addressables.values
.groupBy { it.event?.kind() }
.forEach { Log.d("STATE DUMP", "Kind ${it.key}: \t${it.value.size} elements ") }
LocalCache.notes
.countByGroup { _, it -> it.event?.kind() }
.forEach { Log.d("STATE DUMP", "Kind ${it.key}: \t${it.value} elements ") }
LocalCache.addressables
.countByGroup { _, it -> it.event?.kind() }
.forEach { Log.d("STATE DUMP", "Kind ${it.key}: \t${it.value} elements ") }
}
@OptIn(ExperimentalMaterial3Api::class)

Wyświetl plik

@ -38,7 +38,6 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
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.NotificationFeedFilter
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.Size25dp
import com.vitorpamplona.quartz.events.ChatroomKeyable
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
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() {
fun hasNewItems(
account: Account,

Wyświetl plik

@ -232,7 +232,7 @@ class UserReactionsViewModel(val account: Account) : ViewModel() {
val replies = mutableMapOf<String, Int>()
val takenIntoAccount = mutableSetOf<HexKey>()
LocalCache.noteListCache.forEach {
LocalCache.notes.forEach { _, it ->
val noteEvent = it.event
if (noteEvent != null && !takenIntoAccount.contains(noteEvent.id())) {
if (noteEvent is ReactionEvent) {

Wyświetl plik

@ -59,7 +59,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
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.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer

Wyświetl plik

@ -24,10 +24,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import com.vitorpamplona.amethyst.commons.BaseMediaContent
import com.vitorpamplona.amethyst.commons.MediaUrlImage
import com.vitorpamplona.amethyst.commons.MediaUrlVideo
import com.vitorpamplona.amethyst.commons.RichTextParser
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView

Wyświetl plik

@ -27,9 +27,9 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import com.vitorpamplona.amethyst.commons.BaseMediaContent
import com.vitorpamplona.amethyst.commons.MediaLocalImage
import com.vitorpamplona.amethyst.commons.MediaLocalVideo
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
import com.vitorpamplona.amethyst.commons.richtext.MediaLocalImage
import com.vitorpamplona.amethyst.commons.richtext.MediaLocalVideo
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning

Wyświetl plik

@ -43,10 +43,10 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.BaseMediaContent
import com.vitorpamplona.amethyst.commons.MediaUrlImage
import com.vitorpamplona.amethyst.commons.MediaUrlVideo
import com.vitorpamplona.amethyst.commons.RichTextParser
import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer

Wyświetl plik

@ -911,7 +911,7 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
}
fun getAddressableNoteIfExists(key: String): AddressableNote? {
return LocalCache.addressables[key]
return LocalCache.getAddressableNoteIfExists(key)
}
suspend fun findStatusesForUser(

Wyświetl plik

@ -101,7 +101,7 @@ import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import androidx.lifecycle.viewmodel.compose.viewModel
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.Channel
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
@ -674,24 +674,21 @@ fun ShowVideoStreaming(
streamingInfo?.let { event ->
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 {
CrossfadeCheckIfUrlIsOnline(url, accountViewModel) {
Row(
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 =
remember(it) {
remember(streamingInfo) {
MediaUrlVideo(
url = url,
description = title,
artworkUri = artworkUri,
authorName = author,
description = baseChannel.toBestDisplayName(),
artworkUri = event.image(),
authorName = baseChannel.creatorName(),
uri = event.toNostrUri(),
)
}

Wyświetl plik

@ -112,7 +112,7 @@ import androidx.lifecycle.map
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
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.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache

Wyświetl plik

@ -440,6 +440,8 @@
<string name="connectivity_type_always">Mindig</string>
<string name="connectivity_type_wifi_only">Csak WIFI</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="light">Világos</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_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="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="spamming_users">Spammerek</string>
<string name="muted_button">Lenémítva. Kattínts a feloldásért</string>

Wyświetl plik

@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.benchmark
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.commons.ExpandableTextCutOffCalculator
import com.vitorpamplona.amethyst.commons.nthIndexOf
import com.vitorpamplona.amethyst.commons.richtext.ExpandableTextCutOffCalculator
import com.vitorpamplona.amethyst.commons.richtext.nthIndexOf
import junit.framework.TestCase
import org.junit.Rule
import org.junit.Test

Wyświetl plik

@ -25,10 +25,10 @@ import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.linkedin.urls.detection.UrlDetector
import com.linkedin.urls.detection.UrlDetectorOptions
import com.vitorpamplona.amethyst.commons.HashTagSegment
import com.vitorpamplona.amethyst.commons.ImageSegment
import com.vitorpamplona.amethyst.commons.LinkSegment
import com.vitorpamplona.amethyst.commons.RichTextParser
import com.vitorpamplona.amethyst.commons.richtext.HashTagSegment
import com.vitorpamplona.amethyst.commons.richtext.ImageSegment
import com.vitorpamplona.amethyst.commons.richtext.LinkSegment
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.quartz.events.EmptyTagList
import com.vitorpamplona.quartz.events.ImmutableListOfLists
import junit.framework.TestCase.assertNull

Wyświetl plik

@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.benchmark
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.commons.Robohash
import com.vitorpamplona.amethyst.commons.RobohashAssembler
import com.vitorpamplona.amethyst.commons.robohash.Robohash
import com.vitorpamplona.amethyst.commons.robohash.RobohashAssembler
import okio.Buffer
import okio.buffer
import okio.source

Wyświetl plik

@ -25,58 +25,58 @@ import androidx.benchmark.junit4.measureRepeated
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohashparts.accessory0Seven
import com.vitorpamplona.amethyst.commons.robohashparts.accessory1Nose
import com.vitorpamplona.amethyst.commons.robohashparts.accessory2HornRed
import com.vitorpamplona.amethyst.commons.robohashparts.accessory3Button
import com.vitorpamplona.amethyst.commons.robohashparts.accessory4Satellite
import com.vitorpamplona.amethyst.commons.robohashparts.accessory5Mustache
import com.vitorpamplona.amethyst.commons.robohashparts.accessory6Hat
import com.vitorpamplona.amethyst.commons.robohashparts.accessory7Antenna
import com.vitorpamplona.amethyst.commons.robohashparts.accessory8Brush
import com.vitorpamplona.amethyst.commons.robohashparts.accessory9Horn
import com.vitorpamplona.amethyst.commons.robohashparts.body0Trooper
import com.vitorpamplona.amethyst.commons.robohashparts.body1Thin
import com.vitorpamplona.amethyst.commons.robohashparts.body2Thinnest
import com.vitorpamplona.amethyst.commons.robohashparts.body3Front
import com.vitorpamplona.amethyst.commons.robohashparts.body4Round
import com.vitorpamplona.amethyst.commons.robohashparts.body5Neck
import com.vitorpamplona.amethyst.commons.robohashparts.body6IronMan
import com.vitorpamplona.amethyst.commons.robohashparts.body7NeckThinner
import com.vitorpamplona.amethyst.commons.robohashparts.body8Big
import com.vitorpamplona.amethyst.commons.robohashparts.body9Huge
import com.vitorpamplona.amethyst.commons.robohashparts.eyes0Squint
import com.vitorpamplona.amethyst.commons.robohashparts.eyes1Round
import com.vitorpamplona.amethyst.commons.robohashparts.eyes2Single
import com.vitorpamplona.amethyst.commons.robohashparts.eyes3Scott
import com.vitorpamplona.amethyst.commons.robohashparts.eyes4RoundSingle
import com.vitorpamplona.amethyst.commons.robohashparts.eyes5RoundSmall
import com.vitorpamplona.amethyst.commons.robohashparts.eyes6WallE
import com.vitorpamplona.amethyst.commons.robohashparts.eyes7Bar
import com.vitorpamplona.amethyst.commons.robohashparts.eyes8SmallBar
import com.vitorpamplona.amethyst.commons.robohashparts.eyes9Shield
import com.vitorpamplona.amethyst.commons.robohashparts.face0C3po
import com.vitorpamplona.amethyst.commons.robohashparts.face1Rock
import com.vitorpamplona.amethyst.commons.robohashparts.face2Long
import com.vitorpamplona.amethyst.commons.robohashparts.face3Oval
import com.vitorpamplona.amethyst.commons.robohashparts.face4Cylinder
import com.vitorpamplona.amethyst.commons.robohashparts.face5Baloon
import com.vitorpamplona.amethyst.commons.robohashparts.face6Triangle
import com.vitorpamplona.amethyst.commons.robohashparts.face7Bent
import com.vitorpamplona.amethyst.commons.robohashparts.face8TriangleInv
import com.vitorpamplona.amethyst.commons.robohashparts.face9Square
import com.vitorpamplona.amethyst.commons.robohashparts.mouth0Horz
import com.vitorpamplona.amethyst.commons.robohashparts.mouth1Cylinder
import com.vitorpamplona.amethyst.commons.robohashparts.mouth2Teeth
import com.vitorpamplona.amethyst.commons.robohashparts.mouth3Grid
import com.vitorpamplona.amethyst.commons.robohashparts.mouth4Vert
import com.vitorpamplona.amethyst.commons.robohashparts.mouth5MidOpen
import com.vitorpamplona.amethyst.commons.robohashparts.mouth6Cell
import com.vitorpamplona.amethyst.commons.robohashparts.mouth7Happy
import com.vitorpamplona.amethyst.commons.robohashparts.mouth8Buttons
import com.vitorpamplona.amethyst.commons.robohashparts.mouth9Closed
import com.vitorpamplona.amethyst.commons.parts.accessory0Seven
import com.vitorpamplona.amethyst.commons.parts.accessory1Nose
import com.vitorpamplona.amethyst.commons.parts.accessory2HornRed
import com.vitorpamplona.amethyst.commons.parts.accessory3Button
import com.vitorpamplona.amethyst.commons.parts.accessory4Satellite
import com.vitorpamplona.amethyst.commons.parts.accessory5Mustache
import com.vitorpamplona.amethyst.commons.parts.accessory6Hat
import com.vitorpamplona.amethyst.commons.parts.accessory7Antenna
import com.vitorpamplona.amethyst.commons.parts.accessory8Brush
import com.vitorpamplona.amethyst.commons.parts.accessory9Horn
import com.vitorpamplona.amethyst.commons.parts.body0Trooper
import com.vitorpamplona.amethyst.commons.parts.body1Thin
import com.vitorpamplona.amethyst.commons.parts.body2Thinnest
import com.vitorpamplona.amethyst.commons.parts.body3Front
import com.vitorpamplona.amethyst.commons.parts.body4Round
import com.vitorpamplona.amethyst.commons.parts.body5Neck
import com.vitorpamplona.amethyst.commons.parts.body6IronMan
import com.vitorpamplona.amethyst.commons.parts.body7NeckThinner
import com.vitorpamplona.amethyst.commons.parts.body8Big
import com.vitorpamplona.amethyst.commons.parts.body9Huge
import com.vitorpamplona.amethyst.commons.parts.eyes0Squint
import com.vitorpamplona.amethyst.commons.parts.eyes1Round
import com.vitorpamplona.amethyst.commons.parts.eyes2Single
import com.vitorpamplona.amethyst.commons.parts.eyes3Scott
import com.vitorpamplona.amethyst.commons.parts.eyes4RoundSingle
import com.vitorpamplona.amethyst.commons.parts.eyes5RoundSmall
import com.vitorpamplona.amethyst.commons.parts.eyes6WallE
import com.vitorpamplona.amethyst.commons.parts.eyes7Bar
import com.vitorpamplona.amethyst.commons.parts.eyes8SmallBar
import com.vitorpamplona.amethyst.commons.parts.eyes9Shield
import com.vitorpamplona.amethyst.commons.parts.face0C3po
import com.vitorpamplona.amethyst.commons.parts.face1Rock
import com.vitorpamplona.amethyst.commons.parts.face2Long
import com.vitorpamplona.amethyst.commons.parts.face3Oval
import com.vitorpamplona.amethyst.commons.parts.face4Cylinder
import com.vitorpamplona.amethyst.commons.parts.face5Baloon
import com.vitorpamplona.amethyst.commons.parts.face6Triangle
import com.vitorpamplona.amethyst.commons.parts.face7Bent
import com.vitorpamplona.amethyst.commons.parts.face8TriangleInv
import com.vitorpamplona.amethyst.commons.parts.face9Square
import com.vitorpamplona.amethyst.commons.parts.mouth0Horz
import com.vitorpamplona.amethyst.commons.parts.mouth1Cylinder
import com.vitorpamplona.amethyst.commons.parts.mouth2Teeth
import com.vitorpamplona.amethyst.commons.parts.mouth3Grid
import com.vitorpamplona.amethyst.commons.parts.mouth4Vert
import com.vitorpamplona.amethyst.commons.parts.mouth5MidOpen
import com.vitorpamplona.amethyst.commons.parts.mouth6Cell
import com.vitorpamplona.amethyst.commons.parts.mouth7Happy
import com.vitorpamplona.amethyst.commons.parts.mouth8Buttons
import com.vitorpamplona.amethyst.commons.parts.mouth9Closed
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

Wyświetl plik

@ -18,7 +18,7 @@
* 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
package com.vitorpamplona.amethyst.commons.compose
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue

Wyświetl plik

@ -18,7 +18,7 @@
* 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
package com.vitorpamplona.amethyst.commons.richtext
import androidx.test.ext.junit.runners.AndroidJUnit4
import junit.framework.TestCase.assertEquals

Wyświetl plik

@ -18,7 +18,7 @@
* 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
package com.vitorpamplona.amethyst.commons.richtext
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.events.EmptyTagList
@ -687,7 +687,7 @@ class RichTextParserTest {
@Test
fun testTextToParse() {
val state =
com.vitorpamplona.amethyst.commons.RichTextParser()
RichTextParser()
.parseText(textToParse, EmptyTagList)
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",

Wyświetl plik

@ -18,7 +18,7 @@
* 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
package com.vitorpamplona.amethyst.commons.compose
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue

Wyświetl plik

@ -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++
}
}

Wyświetl plik

@ -18,7 +18,7 @@
* 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
package com.vitorpamplona.amethyst.commons.richtext
class ExpandableTextCutOffCalculator {
companion object {

Wyświetl plik

@ -18,7 +18,7 @@
* 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
package com.vitorpamplona.amethyst.commons.richtext
import androidx.compose.runtime.Immutable
import java.io.File

Wyświetl plik

@ -18,7 +18,7 @@
* 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
package com.vitorpamplona.amethyst.commons.richtext
import android.util.Log
import android.util.Patterns

Wyświetl plik

@ -18,7 +18,7 @@
* 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
package com.vitorpamplona.amethyst.commons.richtext
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.ImmutableList

Wyświetl plik

@ -18,7 +18,7 @@
* 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
package com.vitorpamplona.amethyst.commons.robohash
import android.util.LruCache
import androidx.compose.ui.graphics.vector.ImageVector

Wyświetl plik

@ -18,7 +18,7 @@
* 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
package com.vitorpamplona.amethyst.commons.robohash
import android.util.Log
import com.vitorpamplona.quartz.crypto.CryptoUtils

Wyświetl plik

@ -18,7 +18,7 @@
* 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
package com.vitorpamplona.amethyst.commons.robohash
import android.util.Log
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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.commons.robohashparts.accessory0Seven
import com.vitorpamplona.amethyst.commons.robohashparts.accessory1Nose
import com.vitorpamplona.amethyst.commons.robohashparts.accessory2HornRed
import com.vitorpamplona.amethyst.commons.robohashparts.accessory3Button
import com.vitorpamplona.amethyst.commons.robohashparts.accessory4Satellite
import com.vitorpamplona.amethyst.commons.robohashparts.accessory5Mustache
import com.vitorpamplona.amethyst.commons.robohashparts.accessory6Hat
import com.vitorpamplona.amethyst.commons.robohashparts.accessory7Antenna
import com.vitorpamplona.amethyst.commons.robohashparts.accessory8Brush
import com.vitorpamplona.amethyst.commons.robohashparts.accessory9Horn
import com.vitorpamplona.amethyst.commons.robohashparts.body0Trooper
import com.vitorpamplona.amethyst.commons.robohashparts.body1Thin
import com.vitorpamplona.amethyst.commons.robohashparts.body2Thinnest
import com.vitorpamplona.amethyst.commons.robohashparts.body3Front
import com.vitorpamplona.amethyst.commons.robohashparts.body4Round
import com.vitorpamplona.amethyst.commons.robohashparts.body5Neck
import com.vitorpamplona.amethyst.commons.robohashparts.body6IronMan
import com.vitorpamplona.amethyst.commons.robohashparts.body7NeckThinner
import com.vitorpamplona.amethyst.commons.robohashparts.body8Big
import com.vitorpamplona.amethyst.commons.robohashparts.body9Huge
import com.vitorpamplona.amethyst.commons.robohashparts.eyes0Squint
import com.vitorpamplona.amethyst.commons.robohashparts.eyes1Round
import com.vitorpamplona.amethyst.commons.robohashparts.eyes2Single
import com.vitorpamplona.amethyst.commons.robohashparts.eyes3Scott
import com.vitorpamplona.amethyst.commons.robohashparts.eyes4RoundSingle
import com.vitorpamplona.amethyst.commons.robohashparts.eyes5RoundSmall
import com.vitorpamplona.amethyst.commons.robohashparts.eyes6WallE
import com.vitorpamplona.amethyst.commons.robohashparts.eyes7Bar
import com.vitorpamplona.amethyst.commons.robohashparts.eyes8SmallBar
import com.vitorpamplona.amethyst.commons.robohashparts.eyes9Shield
import com.vitorpamplona.amethyst.commons.robohashparts.face0C3po
import com.vitorpamplona.amethyst.commons.robohashparts.face1Rock
import com.vitorpamplona.amethyst.commons.robohashparts.face2Long
import com.vitorpamplona.amethyst.commons.robohashparts.face3Oval
import com.vitorpamplona.amethyst.commons.robohashparts.face4Cylinder
import com.vitorpamplona.amethyst.commons.robohashparts.face5Baloon
import com.vitorpamplona.amethyst.commons.robohashparts.face6Triangle
import com.vitorpamplona.amethyst.commons.robohashparts.face7Bent
import com.vitorpamplona.amethyst.commons.robohashparts.face8TriangleInv
import com.vitorpamplona.amethyst.commons.robohashparts.face9Square
import com.vitorpamplona.amethyst.commons.robohashparts.mouth0Horz
import com.vitorpamplona.amethyst.commons.robohashparts.mouth1Cylinder
import com.vitorpamplona.amethyst.commons.robohashparts.mouth2Teeth
import com.vitorpamplona.amethyst.commons.robohashparts.mouth3Grid
import com.vitorpamplona.amethyst.commons.robohashparts.mouth4Vert
import com.vitorpamplona.amethyst.commons.robohashparts.mouth5MidOpen
import com.vitorpamplona.amethyst.commons.robohashparts.mouth6Cell
import com.vitorpamplona.amethyst.commons.robohashparts.mouth7Happy
import com.vitorpamplona.amethyst.commons.robohashparts.mouth8Buttons
import com.vitorpamplona.amethyst.commons.robohashparts.mouth9Closed
import com.vitorpamplona.amethyst.commons.robohash.parts.accessory0Seven
import com.vitorpamplona.amethyst.commons.robohash.parts.accessory1Nose
import com.vitorpamplona.amethyst.commons.robohash.parts.accessory2HornRed
import com.vitorpamplona.amethyst.commons.robohash.parts.accessory3Button
import com.vitorpamplona.amethyst.commons.robohash.parts.accessory4Satellite
import com.vitorpamplona.amethyst.commons.robohash.parts.accessory5Mustache
import com.vitorpamplona.amethyst.commons.robohash.parts.accessory6Hat
import com.vitorpamplona.amethyst.commons.robohash.parts.accessory7Antenna
import com.vitorpamplona.amethyst.commons.robohash.parts.accessory8Brush
import com.vitorpamplona.amethyst.commons.robohash.parts.accessory9Horn
import com.vitorpamplona.amethyst.commons.robohash.parts.body0Trooper
import com.vitorpamplona.amethyst.commons.robohash.parts.body1Thin
import com.vitorpamplona.amethyst.commons.robohash.parts.body2Thinnest
import com.vitorpamplona.amethyst.commons.robohash.parts.body3Front
import com.vitorpamplona.amethyst.commons.robohash.parts.body4Round
import com.vitorpamplona.amethyst.commons.robohash.parts.body5Neck
import com.vitorpamplona.amethyst.commons.robohash.parts.body6IronMan
import com.vitorpamplona.amethyst.commons.robohash.parts.body7NeckThinner
import com.vitorpamplona.amethyst.commons.robohash.parts.body8Big
import com.vitorpamplona.amethyst.commons.robohash.parts.body9Huge
import com.vitorpamplona.amethyst.commons.robohash.parts.eyes0Squint
import com.vitorpamplona.amethyst.commons.robohash.parts.eyes1Round
import com.vitorpamplona.amethyst.commons.robohash.parts.eyes2Single
import com.vitorpamplona.amethyst.commons.robohash.parts.eyes3Scott
import com.vitorpamplona.amethyst.commons.robohash.parts.eyes4RoundSingle
import com.vitorpamplona.amethyst.commons.robohash.parts.eyes5RoundSmall
import com.vitorpamplona.amethyst.commons.robohash.parts.eyes6WallE
import com.vitorpamplona.amethyst.commons.robohash.parts.eyes7Bar
import com.vitorpamplona.amethyst.commons.robohash.parts.eyes8SmallBar
import com.vitorpamplona.amethyst.commons.robohash.parts.eyes9Shield
import com.vitorpamplona.amethyst.commons.robohash.parts.face0C3po
import com.vitorpamplona.amethyst.commons.robohash.parts.face1Rock
import com.vitorpamplona.amethyst.commons.robohash.parts.face2Long
import com.vitorpamplona.amethyst.commons.robohash.parts.face3Oval
import com.vitorpamplona.amethyst.commons.robohash.parts.face4Cylinder
import com.vitorpamplona.amethyst.commons.robohash.parts.face5Baloon
import com.vitorpamplona.amethyst.commons.robohash.parts.face6Triangle
import com.vitorpamplona.amethyst.commons.robohash.parts.face7Bent
import com.vitorpamplona.amethyst.commons.robohash.parts.face8TriangleInv
import com.vitorpamplona.amethyst.commons.robohash.parts.face9Square
import com.vitorpamplona.amethyst.commons.robohash.parts.mouth0Horz
import com.vitorpamplona.amethyst.commons.robohash.parts.mouth1Cylinder
import com.vitorpamplona.amethyst.commons.robohash.parts.mouth2Teeth
import com.vitorpamplona.amethyst.commons.robohash.parts.mouth3Grid
import com.vitorpamplona.amethyst.commons.robohash.parts.mouth4Vert
import com.vitorpamplona.amethyst.commons.robohash.parts.mouth5MidOpen
import com.vitorpamplona.amethyst.commons.robohash.parts.mouth6Cell
import com.vitorpamplona.amethyst.commons.robohash.parts.mouth7Happy
import com.vitorpamplona.amethyst.commons.robohash.parts.mouth8Buttons
import com.vitorpamplona.amethyst.commons.robohash.parts.mouth9Closed
import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.encoders.HexValidator
import java.security.MessageDigest

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Brown
import com.vitorpamplona.amethyst.commons.LightBrown
import com.vitorpamplona.amethyst.commons.LightGray
import com.vitorpamplona.amethyst.commons.LightRed
import com.vitorpamplona.amethyst.commons.MediumGray
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.robohash.LightBrown
import com.vitorpamplona.amethyst.commons.robohash.LightGray
import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.robohash.MediumGray
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.MediumGray
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.MediumGray
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Gray
import com.vitorpamplona.amethyst.commons.Yellow
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.robohash.Yellow
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Gray
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Gray
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Brown
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Brown
import com.vitorpamplona.amethyst.commons.DarkYellow
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.robohash.DarkYellow
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Brown
import com.vitorpamplona.amethyst.commons.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Brown
import com.vitorpamplona.amethyst.commons.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Brown
import com.vitorpamplona.amethyst.commons.LightRed
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.robohash.LightRed
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Brown
import com.vitorpamplona.amethyst.commons.LightYellow
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.robohash.LightYellow
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

Wyświetl plik

@ -18,7 +18,7 @@
* 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.robohashparts
package com.vitorpamplona.amethyst.commons.robohash.parts
import androidx.compose.foundation.Image
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.rememberVectorPainter
import androidx.compose.ui.tooling.preview.Preview
import com.vitorpamplona.amethyst.commons.Black
import com.vitorpamplona.amethyst.commons.Brown
import com.vitorpamplona.amethyst.commons.roboBuilder
import com.vitorpamplona.amethyst.commons.robohash.Black
import com.vitorpamplona.amethyst.commons.robohash.Brown
import com.vitorpamplona.amethyst.commons.robohash.roboBuilder
@Preview
@Composable

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