mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-29 08:20:51 +00:00
Merge branch 'main' into main
This commit is contained in:
commit
d682518ddb
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 =
|
||||
|
@ -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?,
|
||||
|
@ -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> {
|
||||
|
@ -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) {
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
@ -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> {
|
||||
|
@ -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
|
||||
|
@ -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> {
|
||||
|
@ -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> {
|
||||
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.dal
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
||||
import com.vitorpamplona.quartz.encoders.ATag
|
||||
import com.vitorpamplona.quartz.events.Event
|
||||
import com.vitorpamplona.quartz.events.EventInterface
|
||||
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
|
||||
import com.vitorpamplona.quartz.events.MuteListEvent
|
||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
class FilterByListParams(
|
||||
val isGlobal: Boolean,
|
||||
val isHiddenList: Boolean,
|
||||
val followLists: Account.LiveFollowLists?,
|
||||
val hiddenLists: Account.LiveHiddenUsers,
|
||||
val now: Long = TimeUtils.now(),
|
||||
) {
|
||||
fun isNotHidden(userHex: String) = !(hiddenLists.hiddenUsers.contains(userHex) || hiddenLists.spammers.contains(userHex))
|
||||
|
||||
fun isNotInTheFuture(noteEvent: Event) = noteEvent.createdAt <= now
|
||||
|
||||
fun isEventInList(noteEvent: Event): Boolean {
|
||||
if (followLists == null) return false
|
||||
|
||||
return if (noteEvent is LiveActivitiesEvent) {
|
||||
noteEvent.participantsIntersect(followLists.users) ||
|
||||
noteEvent.isTaggedHashes(followLists.hashtags) ||
|
||||
noteEvent.isTaggedGeoHashes(followLists.users) ||
|
||||
noteEvent.isTaggedAddressableNotes(followLists.communities)
|
||||
} else {
|
||||
noteEvent.pubKey in followLists.users ||
|
||||
noteEvent.isTaggedHashes(followLists.hashtags) ||
|
||||
noteEvent.isTaggedGeoHashes(followLists.users) ||
|
||||
noteEvent.isTaggedAddressableNotes(followLists.communities)
|
||||
}
|
||||
}
|
||||
|
||||
fun isATagInList(aTag: ATag): Boolean {
|
||||
if (followLists == null) return false
|
||||
|
||||
return aTag.pubKeyHex in followLists.users
|
||||
}
|
||||
|
||||
fun match(
|
||||
noteEvent: EventInterface?,
|
||||
isGlobalRelay: Boolean = true,
|
||||
) = if (noteEvent is Event) match(noteEvent, isGlobalRelay) else false
|
||||
|
||||
fun match(
|
||||
noteEvent: Event,
|
||||
isGlobalRelay: Boolean = true,
|
||||
) = ((isGlobal && isGlobalRelay) || isEventInList(noteEvent)) &&
|
||||
(isHiddenList || isNotHidden(noteEvent.pubKey)) &&
|
||||
isNotInTheFuture(noteEvent)
|
||||
|
||||
fun match(aTag: ATag?) =
|
||||
aTag != null &&
|
||||
(isGlobal || isATagInList(aTag)) &&
|
||||
(isHiddenList || isNotHidden(aTag.pubKeyHex))
|
||||
|
||||
companion object {
|
||||
fun showHiddenKey(
|
||||
selectedListName: String,
|
||||
userHex: String,
|
||||
) = selectedListName == PeopleListEvent.blockListFor(userHex) || selectedListName == MuteListEvent.blockListFor(userHex)
|
||||
|
||||
fun create(
|
||||
userHex: String,
|
||||
selectedListName: String,
|
||||
followLists: Account.LiveFollowLists?,
|
||||
hiddenUsers: Account.LiveHiddenUsers,
|
||||
): FilterByListParams {
|
||||
return FilterByListParams(
|
||||
isGlobal = selectedListName == GLOBAL_FOLLOWS,
|
||||
isHiddenList = showHiddenKey(selectedListName, userHex),
|
||||
followLists = followLists,
|
||||
hiddenLists = hiddenUsers,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -36,7 +36,12 @@ class GeoHashFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
|
||||
}
|
||||
|
||||
override fun feed(): List<Note> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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",
|
@ -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
|
@ -0,0 +1,331 @@
|
||||
/**
|
||||
* Copyright (c) 2024 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.data
|
||||
|
||||
import java.util.concurrent.ConcurrentSkipListMap
|
||||
import java.util.function.BiConsumer
|
||||
|
||||
class LargeCache<K, V> {
|
||||
val cache = ConcurrentSkipListMap<K, V>()
|
||||
|
||||
fun get(key: K) = cache.get(key)
|
||||
|
||||
fun remove(key: K) = cache.remove(key)
|
||||
|
||||
fun size() = cache.size
|
||||
|
||||
fun getOrCreate(
|
||||
key: K,
|
||||
builder: (key: K) -> V,
|
||||
): V {
|
||||
val value = cache.get(key)
|
||||
|
||||
return if (value != null) {
|
||||
value
|
||||
} else {
|
||||
val newObject = builder(key)
|
||||
cache.putIfAbsent(key, newObject) ?: newObject
|
||||
}
|
||||
}
|
||||
|
||||
fun forEach(consumer: BiConsumer<K, V>) {
|
||||
innerForEach(consumer)
|
||||
}
|
||||
|
||||
fun filter(consumer: BiFilter<K, V>): List<V> {
|
||||
val runner = BiFilterCollector(consumer)
|
||||
innerForEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
fun filterIntoSet(consumer: BiFilter<K, V>): Set<V> {
|
||||
val runner = BiFilterUniqueCollector(consumer)
|
||||
innerForEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
fun <R> mapNotNull(consumer: BiMapper<K, V, R?>): List<R> {
|
||||
val runner = BiMapCollector(consumer)
|
||||
innerForEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
fun <R> mapNotNullIntoSet(consumer: BiMapper<K, V, R?>): Set<R> {
|
||||
val runner = BiMapUniqueCollector(consumer)
|
||||
innerForEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
fun <R> mapFlatten(consumer: BiMapper<K, V, Collection<R>?>): List<R> {
|
||||
val runner = BiMapFlattenCollector(consumer)
|
||||
innerForEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
fun <R> mapFlattenIntoSet(consumer: BiMapper<K, V, Collection<R>?>): Set<R> {
|
||||
val runner = BiMapFlattenUniqueCollector(consumer)
|
||||
innerForEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
fun <R> map(consumer: BiNotNullMapper<K, V, R>): List<R> {
|
||||
val runner = BiNotNullMapCollector(consumer)
|
||||
innerForEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
fun sumOf(consumer: BiSumOf<K, V>): Int {
|
||||
val runner = BiSumOfCollector(consumer)
|
||||
innerForEach(runner)
|
||||
return runner.sum
|
||||
}
|
||||
|
||||
fun sumOfLong(consumer: BiSumOfLong<K, V>): Long {
|
||||
val runner = BiSumOfLongCollector(consumer)
|
||||
innerForEach(runner)
|
||||
return runner.sum
|
||||
}
|
||||
|
||||
fun <R> groupBy(consumer: BiNotNullMapper<K, V, R>): Map<R, List<V>> {
|
||||
val runner = BiGroupByCollector(consumer)
|
||||
innerForEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
fun <R> countByGroup(consumer: BiNotNullMapper<K, V, R>): Map<R, Int> {
|
||||
val runner = BiCountByGroupCollector(consumer)
|
||||
innerForEach(runner)
|
||||
return runner.results
|
||||
}
|
||||
|
||||
fun count(consumer: BiFilter<K, V>): Int {
|
||||
val runner = BiCountIfCollector(consumer)
|
||||
innerForEach(runner)
|
||||
return runner.count
|
||||
}
|
||||
|
||||
private fun innerForEach(runner: BiConsumer<K, V>) {
|
||||
// val (value, elapsed) =
|
||||
// measureTimedValue {
|
||||
cache.forEach(runner)
|
||||
// }
|
||||
// println("LargeCache full loop $elapsed \t for $runner")
|
||||
}
|
||||
}
|
||||
|
||||
fun interface BiFilter<K, V> {
|
||||
fun filter(
|
||||
k: K,
|
||||
v: V,
|
||||
): Boolean
|
||||
}
|
||||
|
||||
class BiFilterCollector<K, V>(val filter: BiFilter<K, V>) : BiConsumer<K, V> {
|
||||
var results: ArrayList<V> = ArrayList()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
if (filter.filter(k, v)) {
|
||||
results.add(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiFilterUniqueCollector<K, V>(val filter: BiFilter<K, V>) : BiConsumer<K, V> {
|
||||
var results: HashSet<V> = HashSet()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
if (filter.filter(k, v)) {
|
||||
results.add(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun interface BiMapper<K, V, R> {
|
||||
fun map(
|
||||
k: K,
|
||||
v: V,
|
||||
): R?
|
||||
}
|
||||
|
||||
class BiMapCollector<K, V, R>(val mapper: BiMapper<K, V, R?>) : BiConsumer<K, V> {
|
||||
var results: ArrayList<R> = ArrayList()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val result = mapper.map(k, v)
|
||||
if (result != null) {
|
||||
results.add(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiMapUniqueCollector<K, V, R>(val mapper: BiMapper<K, V, R?>) : BiConsumer<K, V> {
|
||||
var results: HashSet<R> = HashSet()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val result = mapper.map(k, v)
|
||||
if (result != null) {
|
||||
results.add(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiMapFlattenCollector<K, V, R>(val mapper: BiMapper<K, V, Collection<R>?>) : BiConsumer<K, V> {
|
||||
var results: ArrayList<R> = ArrayList()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val result = mapper.map(k, v)
|
||||
if (result != null) {
|
||||
results.addAll(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiMapFlattenUniqueCollector<K, V, R>(val mapper: BiMapper<K, V, Collection<R>?>) : BiConsumer<K, V> {
|
||||
var results: HashSet<R> = HashSet()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val result = mapper.map(k, v)
|
||||
if (result != null) {
|
||||
results.addAll(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun interface BiNotNullMapper<K, V, R> {
|
||||
fun map(
|
||||
k: K,
|
||||
v: V,
|
||||
): R
|
||||
}
|
||||
|
||||
class BiNotNullMapCollector<K, V, R>(val mapper: BiNotNullMapper<K, V, R>) : BiConsumer<K, V> {
|
||||
var results: ArrayList<R> = ArrayList()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
results.add(mapper.map(k, v))
|
||||
}
|
||||
}
|
||||
|
||||
fun interface BiSumOf<K, V> {
|
||||
fun map(
|
||||
k: K,
|
||||
v: V,
|
||||
): Int
|
||||
}
|
||||
|
||||
class BiSumOfCollector<K, V>(val mapper: BiSumOf<K, V>) : BiConsumer<K, V> {
|
||||
var sum = 0
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
sum += mapper.map(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
fun interface BiSumOfLong<K, V> {
|
||||
fun map(
|
||||
k: K,
|
||||
v: V,
|
||||
): Long
|
||||
}
|
||||
|
||||
class BiSumOfLongCollector<K, V>(val mapper: BiSumOfLong<K, V>) : BiConsumer<K, V> {
|
||||
var sum = 0L
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
sum += mapper.map(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
class BiGroupByCollector<K, V, R>(val mapper: BiNotNullMapper<K, V, R>) : BiConsumer<K, V> {
|
||||
var results = HashMap<R, ArrayList<V>>()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val group = mapper.map(k, v)
|
||||
|
||||
val list = results[group]
|
||||
if (list == null) {
|
||||
val answer = ArrayList<V>()
|
||||
answer.add(v)
|
||||
results[group] = answer
|
||||
} else {
|
||||
list.add(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiCountByGroupCollector<K, V, R>(val mapper: BiNotNullMapper<K, V, R>) : BiConsumer<K, V> {
|
||||
var results = HashMap<R, Int>()
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
val group = mapper.map(k, v)
|
||||
|
||||
val count = results[group]
|
||||
if (count == null) {
|
||||
results[group] = 1
|
||||
} else {
|
||||
results[group] = count + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BiCountIfCollector<K, V>(val filter: BiFilter<K, V>) : BiConsumer<K, V> {
|
||||
var count = 0
|
||||
|
||||
override fun accept(
|
||||
k: K,
|
||||
v: V,
|
||||
) {
|
||||
if (filter.filter(k, v)) count++
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* 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 {
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
Loading…
Reference in New Issue
Block a user