Migrating Lint to 1.3.1

This commit is contained in:
Vitor Pamplona
2024-07-03 11:06:02 -04:00
parent 692f7c4270
commit 8d88efd27b
201 changed files with 1858 additions and 1814 deletions
@@ -75,9 +75,21 @@ class ImageUploadTesting {
)
val url = result.tags!!.first { it[0] == "url" }.get(1)
val size = result.tags!!.firstOrNull { it[0] == "size" }?.get(1)?.ifBlank { null }
val dim = result.tags!!.firstOrNull { it[0] == "dim" }?.get(1)?.ifBlank { null }
val hash = result.tags!!.firstOrNull { it[0] == "x" }?.get(1)?.ifBlank { null }
val size =
result.tags!!
.firstOrNull { it[0] == "size" }
?.get(1)
?.ifBlank { null }
val dim =
result.tags!!
.firstOrNull { it[0] == "dim" }
?.get(1)
?.ifBlank { null }
val hash =
result.tags!!
.firstOrNull { it[0] == "x" }
?.get(1)
?.ifBlank { null }
val contentType = result.tags!!.first { it[0] == "m" }.get(1)
val ox = result.tags!!.first { it[0] == "ox" }.get(1)
@@ -79,8 +79,7 @@ class ThreadAssemblerTest {
{"id":"512962dbada5fd5015fc727a107d5c3f569662de67eab8e5da5a8065012cf11e","pubkey":"7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194","created_at":1688194800,"kind":1,"tags":[["a","30023:6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93:1680612926599","","root"],["e","87a5bd25aa084cefb3357fc9c2a5b327254fab35fdd7b2d4bd0acddc63d0abe8","","reply"]],"content":"another one","sig":"b4bc4d8206a08de0a918043129b27c8863d17f60e4f58b4d2b535326625870d32640b04f247eb9eb5df1d62fe98c8e1b0341bdd119655553488300ec9d5b4036"},
{"id":"ce6e32e3e17b6901d2cc70b60f3743e24f885bb6e9da6d88cff516079eac1883","pubkey":"726a1e261cc6474674e8285e3951b3bb139be9a773d1acf49dc868db861a1c11","created_at":1688234921,"kind":1,"tags":[["a","30023:6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93:1680612926599","","root"],["e","512962dbada5fd5015fc727a107d5c3f569662de67eab8e5da5a8065012cf11e","","reply"]],"content":"yet another one, testing 0.0.3","sig":"13bd41c4029c6a7ee41cb03d12e831e9e9b6e14d43a61c78a72657070b45385ba2ce98e8121049fbcbdb7b2dd777c36c7867ba70b5e6a4798a9b866910ae5b62"}
]
"""
.trimIndent()
""".trimIndent()
val header =
"""
@@ -111,8 +110,7 @@ class ThreadAssemblerTest {
["p","b9e76546ba06456ed301d9e52bc49fa48e70a6bf2282be7a1ae72947612023dc"]
]
}
"""
.trimIndent()
""".trimIndent()
@Test
fun threadOrderTest() =
@@ -55,15 +55,11 @@ object PushDistributorHandler : PushDistributorActions {
endpointInternal = ""
}
override fun getSavedDistributor(): String {
return unifiedPush.getSavedDistributor(appContext) ?: ""
}
override fun getSavedDistributor(): String = unifiedPush.getSavedDistributor(appContext) ?: ""
fun savedDistributorExists(): Boolean = getSavedDistributor().isNotEmpty()
override fun getInstalledDistributors(): List<String> {
return unifiedPush.getDistributors(appContext)
}
override fun getInstalledDistributors(): List<String> = unifiedPush.getDistributors(appContext)
fun formattedDistributorNames(): List<String> {
val distributorsArray = getInstalledDistributors().toTypedArray()
@@ -87,8 +83,7 @@ object PushDistributorHandler : PushDistributorActions {
it
}
as String
}
.toTypedArray()
}.toTypedArray()
return distributorsNameArray.toList()
}
@@ -126,8 +126,7 @@ class PushMessageReceiver : MessagingReceiver() {
pushHandler.removeEndpoint()
}
fun notificationManager(): NotificationManager {
return ContextCompat.getSystemService(appContext, NotificationManager::class.java)
fun notificationManager(): NotificationManager =
ContextCompat.getSystemService(appContext, NotificationManager::class.java)
as NotificationManager
}
}
@@ -118,7 +118,5 @@ private class PullRefreshNestedScrollConnection(
else -> Offset.Zero
}
override suspend fun onPreFling(available: Velocity): Velocity {
return Velocity(0f, onRelease(available.y))
}
override suspend fun onPreFling(available: Velocity): Velocity = Velocity(0f, onRelease(available.y))
}
@@ -63,8 +63,7 @@ fun Modifier.pullRefreshIndicatorTransform(
) {
this@drawWithContent.drawContent()
}
}
.graphicsLayer {
}.graphicsLayer {
translationY = state.position - size.height
if (scale && !state.refreshing) {
@@ -61,7 +61,8 @@ class Amethyst : Application() {
}
val coilCache: DiskCache by lazy {
DiskCache.Builder()
DiskCache
.Builder()
.directory(applicationContext.safeCacheDir.resolve("image_cache"))
.maxSizePercent(0.2)
.maximumMaxSizeBytes(500L * 1024 * 1024) // 250MB
@@ -77,10 +78,18 @@ class Amethyst : Application() {
if (BuildConfig.DEBUG) {
StrictMode.setThreadPolicy(
ThreadPolicy.Builder().detectAll().penaltyLog().build(),
ThreadPolicy
.Builder()
.detectAll()
.penaltyLog()
.build(),
)
StrictMode.setVmPolicy(
VmPolicy.Builder().detectAll().penaltyLog().build(),
VmPolicy
.Builder()
.detectAll()
.penaltyLog()
.build(),
)
}
@@ -94,13 +103,9 @@ class Amethyst : Application() {
}
}
fun imageLoaderBuilder(): ImageLoader.Builder {
return ImageLoader.Builder(applicationContext).diskCache { coilCache }
}
fun imageLoaderBuilder(): ImageLoader.Builder = ImageLoader.Builder(applicationContext).diskCache { coilCache }
fun encryptedStorage(npub: String? = null): EncryptedSharedPreferences {
return EncryptedStorage.preferences(instance, npub)
}
fun encryptedStorage(npub: String? = null): EncryptedSharedPreferences = EncryptedStorage.preferences(instance, npub)
companion object {
lateinit var instance: Amethyst
@@ -29,16 +29,15 @@ class EncryptedStorage {
private const val PREFERENCES_NAME = "secret_keeper"
// returns the preferences for each account or a global file if null.
fun prefsFileName(npub: String? = null): String {
return if (npub == null) PREFERENCES_NAME else "${PREFERENCES_NAME}_$npub"
}
fun prefsFileName(npub: String? = null): String = if (npub == null) PREFERENCES_NAME else "${PREFERENCES_NAME}_$npub"
fun preferences(
applicationContext: Context,
npub: String? = null,
): EncryptedSharedPreferences {
val masterKey: MasterKey =
MasterKey.Builder(applicationContext, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
MasterKey
.Builder(applicationContext, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
@@ -33,7 +33,10 @@ import com.vitorpamplona.quartz.encoders.Nip19Bech32
import com.vitorpamplona.quartz.events.Event
import kotlinx.coroutines.Dispatchers
data class Spammer(val pubkeyHex: HexKey, var duplicatedMessages: Set<HexKey>)
data class Spammer(
val pubkeyHex: HexKey,
var duplicatedMessages: Set<HexKey>,
)
class AntiSpamFilter {
val recentMessages = LruCache<Int, String>(1000)
@@ -109,7 +112,9 @@ class AntiSpamFilter {
}
@Stable
class AntiSpamLiveData(val cache: AntiSpamFilter) : LiveData<AntiSpamState>(AntiSpamState(cache)) {
class AntiSpamLiveData(
val cache: AntiSpamFilter,
) : LiveData<AntiSpamState>(AntiSpamState(cache)) {
// Refreshes observers in batches.
private val bundler = BundledUpdate(300, Dispatchers.IO)
@@ -126,4 +131,6 @@ class AntiSpamLiveData(val cache: AntiSpamFilter) : LiveData<AntiSpamState>(Anti
}
}
class AntiSpamState(val cache: AntiSpamFilter)
class AntiSpamState(
val cache: AntiSpamFilter,
)
@@ -37,7 +37,9 @@ import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import kotlinx.coroutines.Dispatchers
@Stable
class PublicChatChannel(idHex: String) : Channel(idHex) {
class PublicChatChannel(
idHex: String,
) : Channel(idHex) {
var info = ChannelCreateEvent.ChannelData(null, null, null)
fun updateChannelInfo(
@@ -49,26 +51,22 @@ class PublicChatChannel(idHex: String) : Channel(idHex) {
super.updateChannelInfo(creator, updatedAt)
}
override fun toBestDisplayName(): String {
return info.name ?: super.toBestDisplayName()
}
override fun toBestDisplayName(): String = info.name ?: super.toBestDisplayName()
override fun summary(): String? {
return info.about
}
override fun summary(): String? = info.about
override fun profilePicture(): String? {
if (info.picture.isNullOrBlank()) return super.profilePicture()
return info.picture ?: super.profilePicture()
}
override fun anyNameStartsWith(prefix: String): Boolean {
return listOfNotNull(info.name, info.about).filter { it.contains(prefix, true) }.isNotEmpty()
}
override fun anyNameStartsWith(prefix: String): Boolean = listOfNotNull(info.name, info.about).filter { it.contains(prefix, true) }.isNotEmpty()
}
@Stable
class LiveActivitiesChannel(val address: ATag) : Channel(address.toTag()) {
class LiveActivitiesChannel(
val address: ATag,
) : Channel(address.toTag()) {
var info: LiveActivitiesEvent? = null
override fun idNote() = address.toNAddr()
@@ -86,27 +84,22 @@ class LiveActivitiesChannel(val address: ATag) : Channel(address.toTag()) {
super.updateChannelInfo(creator, updatedAt)
}
override fun toBestDisplayName(): String {
return info?.title() ?: super.toBestDisplayName()
}
override fun toBestDisplayName(): String = info?.title() ?: super.toBestDisplayName()
override fun summary(): String? {
return info?.summary()
}
override fun summary(): String? = info?.summary()
override fun profilePicture(): String? {
return info?.image()?.ifBlank { null }
}
override fun profilePicture(): String? = info?.image()?.ifBlank { null }
override fun anyNameStartsWith(prefix: String): Boolean {
return listOfNotNull(info?.title(), info?.summary())
override fun anyNameStartsWith(prefix: String): Boolean =
listOfNotNull(info?.title(), info?.summary())
.filter { it.contains(prefix, true) }
.isNotEmpty()
}
}
@Stable
abstract class Channel(val idHex: String) {
abstract class Channel(
val idHex: String,
) {
var creator: User? = null
var updatedMetadataAt: Long = 0
val notes = LargeCache<HexKey, Note>()
@@ -118,21 +111,13 @@ abstract class Channel(val idHex: String) {
open fun idDisplayNote() = idNote().toShortenHex()
open fun toBestDisplayName(): String {
return idDisplayNote()
}
open fun toBestDisplayName(): String = idDisplayNote()
open fun summary(): String? {
return null
}
open fun summary(): String? = null
open fun creatorName(): String? {
return creator?.toBestDisplayName()
}
open fun creatorName(): String? = creator?.toBestDisplayName()
open fun profilePicture(): String? {
return creator?.info?.banner
}
open fun profilePicture(): String? = creator?.info?.banner
open fun updateChannelInfo(
creator: User,
@@ -167,10 +152,10 @@ abstract class Channel(val idHex: String) {
fun pruneOldAndHiddenMessages(account: Account): Set<Note> {
val important =
notes.filter { key, it ->
it.author?.let { author -> account.isHidden(author) } == false
}
.sortedWith(DefaultFeedOrder)
notes
.filter { key, it ->
it.author?.let { author -> account.isHidden(author) } == false
}.sortedWith(DefaultFeedOrder)
.take(500)
.toSet()
@@ -182,7 +167,9 @@ abstract class Channel(val idHex: String) {
}
}
class ChannelLiveData(val channel: Channel) : LiveData<ChannelState>(ChannelState(channel)) {
class ChannelLiveData(
val channel: Channel,
) : LiveData<ChannelState>(ChannelState(channel)) {
// Refreshes observers in batches.
private val bundler = BundledUpdate(300, Dispatchers.IO)
@@ -208,4 +195,6 @@ class ChannelLiveData(val channel: Channel) : LiveData<ChannelState>(ChannelStat
}
}
class ChannelState(val channel: Channel)
class ChannelState(
val channel: Channel,
)
@@ -26,7 +26,7 @@ import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.utils.TimeUtils
@Stable
class Chatroom() {
class Chatroom {
var authors: Set<User> = setOf()
var roomMessages: Set<Note> = setOf()
var subject: String? = null
@@ -72,9 +72,7 @@ class Chatroom() {
}
}
fun senderIntersects(keySet: Set<HexKey>): Boolean {
return authors.any { it.pubkeyHex in keySet }
}
fun senderIntersects(keySet: Set<HexKey>): Boolean = authors.any { it.pubkeyHex in keySet }
fun pruneMessagesToTheLatestOnly(): Set<Note> {
val sorted = roomMessages.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
@@ -68,8 +68,8 @@ fun RenderHashTagIcons() {
}
}
fun checkForHashtagWithIcon(tag: String): HashtagIcon? {
return when (tag.lowercase()) {
fun checkForHashtagWithIcon(tag: String): HashtagIcon? =
when (tag.lowercase()) {
"₿itcoin", "bitcoin", "btc", "timechain", "bitcoiner", "bitcoiners" -> bitcoin
"nostr", "nostrich", "nostriches", "thenostr" -> nostr
"lightning", "lightningnetwork" -> lightning
@@ -86,7 +86,6 @@ fun checkForHashtagWithIcon(tag: String): HashtagIcon? {
"weed", "weedstr", "420", "cannabis", "marijuana" -> weed
else -> null
}
}
val bitcoin = HashtagIcon(CustomHashTagIcons.Btc, "Bitcoin", Modifier.padding(start = 1.dp, bottom = 1.dp, top = 1.dp))
val nostr = HashtagIcon(CustomHashTagIcons.Nostr, "Nostr", Modifier.padding(start = 1.dp, bottom = 1.dp, top = 1.dp))
@@ -66,9 +66,7 @@ class HumanName(
var family: String? = null,
var given: ArrayList<String> = arrayListOf(),
) {
fun assembleName(): String {
return given.joinToString(" ") + " " + family
}
fun assembleName(): String = given.joinToString(" ") + " " + family
}
class Bundle(
@@ -62,8 +62,7 @@ class ThreadAssembler {
} else {
null
}
}
?.filterNotNull()
}?.filterNotNull()
if (roots != null && roots.isNotEmpty()) {
return roots[0]
@@ -148,9 +147,7 @@ class OnlyLatestVersionSet : MutableSet<Note> {
}
}
override fun addAll(elements: Collection<Note>): Boolean {
return elements.map { add(it) }.any()
}
override fun addAll(elements: Collection<Note>): Boolean = elements.map { add(it) }.any()
override val size: Int
get() = set.size
@@ -160,29 +157,17 @@ class OnlyLatestVersionSet : MutableSet<Note> {
map.clear()
}
override fun isEmpty(): Boolean {
return set.isEmpty()
}
override fun isEmpty(): Boolean = set.isEmpty()
override fun containsAll(elements: Collection<Note>): Boolean {
return set.containsAll(elements)
}
override fun containsAll(elements: Collection<Note>): Boolean = set.containsAll(elements)
override fun contains(element: Note): Boolean {
return set.contains(element)
}
override fun contains(element: Note): Boolean = set.contains(element)
override fun iterator(): MutableIterator<Note> {
return set.iterator()
}
override fun iterator(): MutableIterator<Note> = set.iterator()
override fun retainAll(elements: Collection<Note>): Boolean {
return set.retainAll(elements)
}
override fun retainAll(elements: Collection<Note>): Boolean = set.retainAll(elements)
override fun removeAll(elements: Collection<Note>): Boolean {
return elements.map { remove(it) }.any()
}
override fun removeAll(elements: Collection<Note>): Boolean = elements.map { remove(it) }.any()
override fun remove(element: Note): Boolean {
element.address()?.let {
@@ -27,16 +27,20 @@ import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
data class LevelSignature(val signature: String, val createdAt: Long?, val author: User?)
data class LevelSignature(
val signature: String,
val createdAt: Long?,
val author: User?,
)
object ThreadLevelCalculator {
val levelFormatter = DateTimeFormatter.ofPattern("uuuu-MM-dd-HH:mm:ss")
private fun formattedDateTime(timestamp: Long): String {
return Instant.ofEpochSecond(timestamp)
private fun formattedDateTime(timestamp: Long): String =
Instant
.ofEpochSecond(timestamp)
.atZone(ZoneId.systemDefault())
.format(levelFormatter)
}
/**
* This method caches signatures during each execution to avoid recalculation in longer threads
@@ -76,8 +80,7 @@ object ThreadLevelCalculator {
accountFollowingSet,
now,
).apply { cachedSignatures.put(it, this) }
}
.maxByOrNull { it.signature.length }
}.maxByOrNull { it.signature.length }
)
val parentSignature = parent?.signature?.removeSuffix(";") ?: ""
@@ -74,7 +74,6 @@ object UrlCachedPreviewer {
onReady(state)
}
},
)
.fetchUrlPreview()
).fetchUrlPreview()
}
}
@@ -27,7 +27,10 @@ import com.vitorpamplona.quartz.events.Event
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
class LatestByKindAndAuthor<T : Event>(private val kind: Int, private val pubkey: String) {
class LatestByKindAndAuthor<T : Event>(
private val kind: Int,
private val pubkey: String,
) {
private val _latest = MutableStateFlow<T?>(null)
val latest = _latest.asStateFlow()
@@ -41,30 +44,30 @@ class LatestByKindAndAuthor<T : Event>(private val kind: Int, private val pubkey
}
}
fun canDelete(): Boolean {
return _latest.subscriptionCount.value == 0
}
fun canDelete(): Boolean = _latest.subscriptionCount.value == 0
suspend fun init() {
val latestNote =
if ((kind in 10000..19999) || (kind in 30000..39999)) {
LocalCache.addressables.maxOrNullOf(
filter = { idHex: String, note: AddressableNote ->
note.event?.let {
it.kind() == kind && it.pubKey() == pubkey
} == true
},
comparator = CreatedAtComparatorAddresses,
)?.event as? T
LocalCache.addressables
.maxOrNullOf(
filter = { idHex: String, note: AddressableNote ->
note.event?.let {
it.kind() == kind && it.pubKey() == pubkey
} == true
},
comparator = CreatedAtComparatorAddresses,
)?.event as? T
} else {
LocalCache.notes.maxOrNullOf(
filter = { idHex: String, note: Note ->
note.event?.let {
it.kind() == kind && it.pubKey() == pubkey
} == true
},
comparator = CreatedAtComparator,
)?.event as? T
LocalCache.notes
.maxOrNullOf(
filter = { idHex: String, note: Note ->
note.event?.let {
it.kind() == kind && it.pubKey() == pubkey
} == true
},
comparator = CreatedAtComparator,
)?.event as? T
}
_latest.tryEmit(latestNote)
@@ -26,7 +26,10 @@ import com.vitorpamplona.quartz.events.Event
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
class LatestByKindWithETag<T : Event>(private val kind: Int, private val eTag: String) {
class LatestByKindWithETag<T : Event>(
private val kind: Int,
private val eTag: String,
) {
private val _latest = MutableStateFlow<T?>(null)
val latest = _latest.asStateFlow()
@@ -40,20 +43,19 @@ class LatestByKindWithETag<T : Event>(private val kind: Int, private val eTag: S
}
}
fun canDelete(): Boolean {
return _latest.subscriptionCount.value == 0
}
fun canDelete(): Boolean = _latest.subscriptionCount.value == 0
suspend fun init() {
val latestNote =
LocalCache.notes.maxOrNullOf(
filter = { idHex: String, note: Note ->
note.event?.let {
it.kind() == kind && it.isTaggedEvent(eTag)
} == true
},
comparator = CreatedAtComparator,
)?.event as? T
LocalCache.notes
.maxOrNullOf(
filter = { idHex: String, note: Note ->
note.event?.let {
it.kind() == kind && it.isTaggedEvent(eTag)
} == true
},
comparator = CreatedAtComparator,
)?.event as? T
_latest.tryEmit(latestNote)
}
@@ -26,7 +26,9 @@ import com.vitorpamplona.ammolite.relays.Relay
import com.vitorpamplona.quartz.events.AddressableEvent
import com.vitorpamplona.quartz.events.Event
abstract class AmethystNostrDataSource(debugName: String) : NostrDataSource(debugName) {
abstract class AmethystNostrDataSource(
debugName: String,
) : NostrDataSource(debugName) {
override fun consume(
event: Event,
relay: Relay,
@@ -59,9 +59,7 @@ class BlurHashFetcher(
data: Uri,
options: Options,
imageLoader: ImageLoader,
): Fetcher {
return BlurHashFetcher(options, data)
}
): Fetcher = BlurHashFetcher(options, data)
}
}
@@ -72,7 +70,8 @@ object BlurHashRequester {
): ImageRequest {
val encodedMessage = URLEncoder.encode(message, "utf-8")
return ImageRequest.Builder(context)
return ImageRequest
.Builder(context)
.data("bluehash:$encodedMessage")
.fetcherFactory(BlurHashFetcher.Factory)
.build()
@@ -22,9 +22,7 @@ package com.vitorpamplona.amethyst.service
import com.vitorpamplona.quartz.events.ImmutableListOfLists
fun String.isUTF16Char(pos: Int): Boolean {
return Character.charCount(this.codePointAt(pos)) == 2
}
fun String.isUTF16Char(pos: Int): Boolean = Character.charCount(this.codePointAt(pos)) == 2
fun String.firstFullCharOld(): String {
return when (this.length) {
@@ -236,10 +236,10 @@ fun MediaMetadataRetriever.prepareVideoHeight(): Int? {
}
}
class ByteArrayMediaDataSource(var imageData: ByteArray) : MediaDataSource() {
override fun getSize(): Long {
return imageData.size.toLong()
}
class ByteArrayMediaDataSource(
var imageData: ByteArray,
) : MediaDataSource() {
override fun getSize(): Long = imageData.size.toLong()
@Throws(IOException::class)
override fun readAt(
@@ -33,7 +33,9 @@ import androidx.compose.runtime.mutableStateOf
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.MutableStateFlow
class LocationUtil(context: Context) {
class LocationUtil(
context: Context,
) {
companion object {
const val MIN_TIME: Long = 1000L
const val MIN_DISTANCE: Float = 0.0f
@@ -96,9 +98,7 @@ class LocationUtil(context: Context) {
object CachedGeoLocations {
val locationNames = LruCache<String, String>(20)
fun cached(geoHashStr: String): String? {
return locationNames[geoHashStr]
}
fun cached(geoHashStr: String): String? = locationNames[geoHashStr]
suspend fun geoLocate(
geoHashStr: String,
@@ -31,4 +31,6 @@ fun checkNotInMainThread() {
fun isMainThread() = Looper.myLooper() == Looper.getMainLooper()
class OnMainThreadException(str: String) : RuntimeException(str)
class OnMainThreadException(
str: String,
) : RuntimeException(str)
@@ -38,7 +38,10 @@ object Nip96MediaServers {
ServerName("Void.cat", "https://void.cat"),
)
data class ServerName(val name: String, val baseUrl: String)
data class ServerName(
val name: String,
val baseUrl: String,
)
val cache: MutableMap<String, Nip96Retriever.ServerInfo> = mutableMapOf()
@@ -83,7 +86,8 @@ class Nip96Retriever {
checkNotInMainThread()
val request: Request =
Request.Builder()
Request
.Builder()
.header("Accept", "application/nostr+json")
.url(baseUrl.removeSuffix("/") + "/.well-known/nostr/nip96.json")
.build()
@@ -32,7 +32,10 @@ import okhttp3.Request
import okio.ByteString.Companion.toByteString
import kotlin.coroutines.cancellation.CancellationException
@Immutable data class OnlineCheckResult(val timeInMs: Long, val online: Boolean)
@Immutable data class OnlineCheckResult(
val timeInMs: Long,
val online: Boolean,
)
object OnlineChecker {
val checkOnlineCache = LruCache<String, OnlineCheckResult>(100)
@@ -58,7 +61,8 @@ object OnlineChecker {
val result =
if (url.startsWith("wss")) {
val request =
Request.Builder()
Request
.Builder()
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
.url(url.replace("wss+livekit://", "wss://"))
.header("Upgrade", "websocket")
@@ -69,7 +73,9 @@ object OnlineChecker {
.build()
val client =
HttpClientManager.getHttpClient().newBuilder()
HttpClientManager
.getHttpClient()
.newBuilder()
.eventListener(EventListener.NONE)
.protocols(listOf(Protocol.HTTP_1_1))
.build()
@@ -80,7 +86,8 @@ object OnlineChecker {
}
} else {
val request =
Request.Builder()
Request
.Builder()
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
.url(url)
.get()
@@ -28,15 +28,12 @@ object PackageUtils {
private fun isPackageInstalled(
context: Context,
target: String,
): Boolean {
return context.packageManager.getInstalledApplications(0).find { info ->
): Boolean =
context.packageManager.getInstalledApplications(0).find { info ->
info.packageName == target
} != null
}
fun isOrbotInstalled(context: Context): Boolean {
return isPackageInstalled(context, "org.torproject.android")
}
fun isOrbotInstalled(context: Context): Boolean = isPackageInstalled(context, "org.torproject.android")
fun isExternalSignerInstalled(context: Context): Boolean {
val intent =
@@ -27,14 +27,15 @@ import kotlinx.coroutines.CancellationException
import java.text.NumberFormat
@Stable
data class InvoiceAmount(val invoice: String, val amount: String?)
data class InvoiceAmount(
val invoice: String,
val amount: String?,
)
object CachedLnInvoiceParser {
val lnInvoicesCache = LruCache<String, InvoiceAmount>(20)
fun cached(lnurl: String): InvoiceAmount? {
return lnInvoicesCache[lnurl]
}
fun cached(lnurl: String): InvoiceAmount? = lnInvoicesCache[lnurl]
fun parse(lnbcWord: String): InvoiceAmount? {
val myInvoice = LnInvoiceUtil.findInvoice(lnbcWord)
@@ -42,7 +42,8 @@ class OkHttpBlockstreamExplorer : BitcoinExplorer {
val url = "$BLOCKSTREAM_API_URL/block/$hash"
val request =
Request.Builder()
Request
.Builder()
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
.header("Accept", "application/json")
.url(url)
@@ -79,7 +80,8 @@ class OkHttpBlockstreamExplorer : BitcoinExplorer {
val url = "$BLOCKSTREAM_API_URL/block-height/$height"
val request =
Request.Builder()
Request
.Builder()
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
.url(url)
.get()
@@ -33,7 +33,10 @@ import java.util.concurrent.BlockingQueue
/**
* For making async calls to a calendar server
*/
class OkHttpCalendarAsyncSubmit(private val url: String, private val digest: ByteArray) : ICalendarAsyncSubmit {
class OkHttpCalendarAsyncSubmit(
private val url: String,
private val digest: ByteArray,
) : ICalendarAsyncSubmit {
private var queue: BlockingQueue<Optional<Timestamp>>? = null
fun setQueue(queue: BlockingQueue<Optional<Timestamp>>?) {
@@ -49,7 +52,8 @@ class OkHttpCalendarAsyncSubmit(private val url: String, private val digest: Byt
val requestBody = digest.toRequestBody(mediaType)
val request =
okhttp3.Request.Builder()
okhttp3.Request
.Builder()
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
.header("Accept", "application/vnd.opentimestamps.v1")
.header("Content-Type", "application/x-www-form-urlencoded")
@@ -25,14 +25,10 @@ import com.vitorpamplona.quartz.ots.ICalendar
import com.vitorpamplona.quartz.ots.ICalendarAsyncSubmit
class OkHttpCalendarBuilder : CalendarBuilder {
override fun newSyncCalendar(url: String): ICalendar {
return OkHttpCalendar(url)
}
override fun newSyncCalendar(url: String): ICalendar = OkHttpCalendar(url)
override fun newAsyncCalendar(
url: String,
digest: ByteArray,
): ICalendarAsyncSubmit {
return OkHttpCalendarAsyncSubmit(url, digest)
}
): ICalendarAsyncSubmit = OkHttpCalendarAsyncSubmit(url, digest)
}
@@ -59,12 +59,12 @@ class VideoCache {
// This method should be called when proxy setting changes.
fun renewCacheFactory(client: OkHttpClient) {
cacheDataSourceFactory =
CacheDataSource.Factory()
CacheDataSource
.Factory()
.setCache(simpleCache)
.setUpstreamDataSourceFactory(
OkHttpDataSource.Factory(client),
)
.setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
).setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
}
fun get(client: OkHttpClient): CacheDataSource.Factory {
@@ -32,7 +32,5 @@ class VideoViewedPositionCache {
cachedPosition.put(uri, position)
}
fun get(uri: String): Long? {
return cachedPosition.get(uri)
}
fun get(uri: String): Long? = cachedPosition.get(uri)
}
@@ -44,11 +44,7 @@ class WssDataStreamCollector : WebSocketListener() {
wssData.removeAll(wssData)
}
fun canStream(): Boolean {
return wssData.size > 0
}
fun canStream(): Boolean = wssData.size > 0
fun getNextStream(): ByteString {
return wssData.pollFirst()
}
fun getNextStream(): ByteString = wssData.pollFirst()
}
@@ -32,7 +32,9 @@ import okhttp3.WebSocket
import kotlin.math.min
@OptIn(UnstableApi::class)
class WssStreamDataSource(val httpClient: OkHttpClient) : BaseDataSource(true) {
class WssStreamDataSource(
val httpClient: OkHttpClient,
) : BaseDataSource(true) {
val dataStreamCollector: WssDataStreamCollector = WssDataStreamCollector()
var webSocketClient: WebSocket? = null
@@ -46,11 +48,14 @@ class WssStreamDataSource(val httpClient: OkHttpClient) : BaseDataSource(true) {
// which collects the data for us (Previous class).
webSocketClient =
httpClient.newWebSocket(
Request.Builder().apply {
dataSpec.httpRequestHeaders.forEach { entry ->
addHeader(entry.key, entry.value)
}
}.url(dataSpec.uri.toString()).build(),
Request
.Builder()
.apply {
dataSpec.httpRequestHeaders.forEach { entry ->
addHeader(entry.key, entry.value)
}
}.url(dataSpec.uri.toString())
.build(),
dataStreamCollector,
)
@@ -106,7 +111,9 @@ class WssStreamDataSource(val httpClient: OkHttpClient) : BaseDataSource(true) {
}
// Factory class for DataSource
class Factory(val okHttpClient: OkHttpClient) : DataSource.Factory {
class Factory(
val okHttpClient: OkHttpClient,
) : DataSource.Factory {
override fun createDataSource(): DataSource = WssStreamDataSource(okHttpClient)
}
}
@@ -22,7 +22,10 @@ package com.vitorpamplona.amethyst.service.previews
import kotlinx.coroutines.CancellationException
class BahaUrlPreview(val url: String, var callback: IUrlPreviewCallback?) {
class BahaUrlPreview(
val url: String,
var callback: IUrlPreviewCallback?,
) {
suspend fun fetchUrlPreview(timeOut: Int = 30000) =
try {
fetch(timeOut)
@@ -40,11 +40,7 @@ class UrlInfoItem(
image
}
fun fetchComplete(): Boolean {
return url.isNotEmpty() && image.isNotEmpty()
}
fun fetchComplete(): Boolean = url.isNotEmpty() && image.isNotEmpty()
fun allFetchComplete(): Boolean {
return title.isNotEmpty() && description.isNotEmpty() && image.isNotEmpty()
}
fun allFetchComplete(): Boolean = title.isNotEmpty() && description.isNotEmpty() && image.isNotEmpty()
}
@@ -71,7 +71,12 @@ suspend fun getDocument(
timeOut: Int = 30000,
): UrlInfoItem =
withContext(Dispatchers.IO) {
val request: Request = Request.Builder().url(url).get().build()
val request: Request =
Request
.Builder()
.url(url)
.get()
.build()
HttpClientManager.getHttpClient().newCall(request).execute().use {
checkNotInMainThread()
if (it.isSuccessful) {
@@ -132,8 +137,8 @@ private val UNICODE_BOMS =
"ffff0000".decodeHex(),
)
private fun BufferedSource.readBomAsCharset(): Charset? {
return when (select(UNICODE_BOMS)) {
private fun BufferedSource.readBomAsCharset(): Charset? =
when (select(UNICODE_BOMS)) {
0 -> Charsets.UTF_8
1 -> Charsets.UTF_16BE
2 -> Charsets.UTF_16LE
@@ -142,7 +147,6 @@ private fun BufferedSource.readBomAsCharset(): Charset? {
-1 -> null
else -> throw AssertionError()
}
}
private val RE_CONTENT_TYPE_CHARSET = Regex("""charset=([^;]+)""")
@@ -158,7 +162,8 @@ private fun detectCharset(bodyBytes: ByteArray): Charset {
}
}
if (meta.attr(ATTRIBUTE_VALUE_HTTP_EQUIV).lowercase() == "content-type") {
RE_CONTENT_TYPE_CHARSET.find(meta.attr(CONTENT))
RE_CONTENT_TYPE_CHARSET
.find(meta.attr(CONTENT))
?.let {
runCatching { Charset.forName(it.groupValues[1]) }.getOrNull()
}?.let {
@@ -23,7 +23,9 @@ package com.vitorpamplona.amethyst.service.relays
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.ammolite.relays.EOSETime
class EOSERelayList(var relayList: Map<String, EOSETime> = emptyMap()) {
class EOSERelayList(
var relayList: Map<String, EOSETime> = emptyMap(),
) {
fun addOrUpdate(
relayUrl: String,
time: Long,
@@ -37,7 +39,9 @@ class EOSERelayList(var relayList: Map<String, EOSETime> = emptyMap()) {
}
}
class EOSEFollowList(var followList: Map<String, EOSERelayList> = emptyMap()) {
class EOSEFollowList(
var followList: Map<String, EOSERelayList> = emptyMap(),
) {
fun addOrUpdate(
listCode: String,
relayUrl: String,
@@ -54,7 +58,9 @@ class EOSEFollowList(var followList: Map<String, EOSERelayList> = emptyMap()) {
}
}
class EOSEAccount(var users: Map<User, EOSEFollowList> = emptyMap()) {
class EOSEAccount(
var users: Map<User, EOSEFollowList> = emptyMap(),
) {
fun addOrUpdate(
user: User,
listCode: String,
@@ -28,150 +28,152 @@ import androidx.lifecycle.LifecycleOwner
import java.lang.ref.WeakReference
import java.util.Locale
class TextToSpeechHelper private constructor(private val context: WeakReference<Context>) :
LifecycleEventObserver {
private val appContext
get() = context.get()!!.applicationContext
class TextToSpeechHelper private constructor(
private val context: WeakReference<Context>,
) : LifecycleEventObserver {
private val appContext
get() = context.get()!!.applicationContext
private var message: String? = null
private var message: String? = null
private var ttsEngine: TextToSpeechEngine? = null
private var ttsEngine: TextToSpeechEngine? = null
private var onStart: (() -> Unit)? = null
private var onStart: (() -> Unit)? = null
private var onDoneListener: (() -> Unit)? = null
private var onDoneListener: (() -> Unit)? = null
private var onErrorListener: ((String) -> Unit)? = null
private var onErrorListener: ((String) -> Unit)? = null
private var onHighlightListener: ((Pair<Int, Int>) -> Unit)? = null
private var onHighlightListener: ((Pair<Int, Int>) -> Unit)? = null
private var customActionForDestroy: (() -> Unit)? = null
private var customActionForDestroy: (() -> Unit)? = null
init {
Log.d("Init", "Init TTS")
init {
Log.d("Init", "Init TTS")
initTTS()
}
fun registerLifecycle(owner: LifecycleOwner): TextToSpeechHelper {
owner.lifecycle.addObserver(this)
return this
}
private fun initTTS() =
context.get()?.run {
ttsEngine =
TextToSpeechEngine
.getInstance()
.setOnCompletionListener { onDoneListener?.invoke() }
.setOnErrorListener { onErrorListener?.invoke(it) }
.setOnStartListener { onStart?.invoke() }
}
fun speak(message: String): TextToSpeechHelper {
if (ttsEngine == null) {
initTTS()
}
this.message = message
fun registerLifecycle(owner: LifecycleOwner): TextToSpeechHelper {
owner.lifecycle.addObserver(this)
return this
ttsEngine?.initTTS(
appContext,
message,
)
return this
}
/**
* This method will highlight the text in the textView
*
* @exception Exception("Message can't be null for highlighting !! Call speak() first")
*/
fun highlight(): TextToSpeechHelper {
if (message == null) {
throw Exception("Message can't be null for highlighting !! Call speak() first")
}
ttsEngine?.setHighlightedMessage(message!!)
ttsEngine?.setOnHighlightListener { i, i2 -> onHighlightListener?.invoke(Pair(i, i2)) }
return this
}
private fun initTTS() =
context.get()?.run {
ttsEngine =
TextToSpeechEngine.getInstance()
.setOnCompletionListener { onDoneListener?.invoke() }
.setOnErrorListener { onErrorListener?.invoke(it) }
.setOnStartListener { onStart?.invoke() }
}
fun removeHighlight(): TextToSpeechHelper {
message = null
onHighlightListener = null
return this
}
fun speak(message: String): TextToSpeechHelper {
if (ttsEngine == null) {
initTTS()
}
this.message = message
fun destroy(action: (() -> Unit) = {}) {
ttsEngine?.destroy()
ttsEngine = null
action.invoke()
instance = null
}
ttsEngine?.initTTS(
appContext,
message,
)
return this
}
fun onStart(onStartListener: () -> Unit): TextToSpeechHelper {
this.onStart = onStartListener
return this
}
/**
* This method will highlight the text in the textView
*
* @exception Exception("Message can't be null for highlighting !! Call speak() first")
*/
fun highlight(): TextToSpeechHelper {
if (message == null) {
throw Exception("Message can't be null for highlighting !! Call speak() first")
}
ttsEngine?.setHighlightedMessage(message!!)
ttsEngine?.setOnHighlightListener { i, i2 -> onHighlightListener?.invoke(Pair(i, i2)) }
return this
}
fun onDone(onCompleteListener: () -> Unit): TextToSpeechHelper {
this.onDoneListener = onCompleteListener
return this
}
fun removeHighlight(): TextToSpeechHelper {
message = null
onHighlightListener = null
return this
}
fun onError(onErrorListener: (String) -> Unit): TextToSpeechHelper {
this.onErrorListener = onErrorListener
return this
}
fun destroy(action: (() -> Unit) = {}) {
ttsEngine?.destroy()
ttsEngine = null
action.invoke()
instance = null
}
fun onHighlight(onHighlightListener: (Pair<Int, Int>) -> Unit): TextToSpeechHelper {
this.onHighlightListener = onHighlightListener
return this
}
fun onStart(onStartListener: () -> Unit): TextToSpeechHelper {
this.onStart = onStartListener
return this
}
fun setCustomActionForDestroy(action: () -> Unit): TextToSpeechHelper {
customActionForDestroy = action
return this
}
fun onDone(onCompleteListener: () -> Unit): TextToSpeechHelper {
this.onDoneListener = onCompleteListener
return this
}
fun setLanguage(locale: Locale): TextToSpeechHelper {
ttsEngine?.setLanguage(locale)
return this
}
fun onError(onErrorListener: (String) -> Unit): TextToSpeechHelper {
this.onErrorListener = onErrorListener
return this
}
fun setPitchAndSpeed(
pitch: Float = DEF_SPEECH_AND_PITCH,
speed: Float = DEF_SPEECH_AND_PITCH,
): TextToSpeechHelper {
ttsEngine?.setPitchAndSpeed(pitch, speed)
return this
}
fun onHighlight(onHighlightListener: (Pair<Int, Int>) -> Unit): TextToSpeechHelper {
this.onHighlightListener = onHighlightListener
return this
}
fun resetPitchAndSpeed(): TextToSpeechHelper {
ttsEngine?.resetPitchAndSpeed()
return this
}
fun setCustomActionForDestroy(action: () -> Unit): TextToSpeechHelper {
customActionForDestroy = action
return this
}
companion object {
private var instance: TextToSpeechHelper? = null
fun setLanguage(locale: Locale): TextToSpeechHelper {
ttsEngine?.setLanguage(locale)
return this
}
fun setPitchAndSpeed(
pitch: Float = DEF_SPEECH_AND_PITCH,
speed: Float = DEF_SPEECH_AND_PITCH,
): TextToSpeechHelper {
ttsEngine?.setPitchAndSpeed(pitch, speed)
return this
}
fun resetPitchAndSpeed(): TextToSpeechHelper {
ttsEngine?.resetPitchAndSpeed()
return this
}
companion object {
private var instance: TextToSpeechHelper? = null
fun getInstance(context: Context): TextToSpeechHelper {
synchronized(TextToSpeechHelper::class.java) {
if (instance == null) {
instance = TextToSpeechHelper(WeakReference(context))
}
return instance!!
fun getInstance(context: Context): TextToSpeechHelper {
synchronized(TextToSpeechHelper::class.java) {
if (instance == null) {
instance = TextToSpeechHelper(WeakReference(context))
}
}
}
override fun onStateChanged(
source: LifecycleOwner,
event: Lifecycle.Event,
) {
if (
event == Lifecycle.Event.ON_DESTROY ||
event == Lifecycle.Event.ON_STOP ||
event == Lifecycle.Event.ON_PAUSE
) {
destroy { customActionForDestroy?.invoke() }
return instance!!
}
}
}
override fun onStateChanged(
source: LifecycleOwner,
event: Lifecycle.Event,
) {
if (
event == Lifecycle.Event.ON_DESTROY ||
event == Lifecycle.Event.ON_STOP ||
event == Lifecycle.Event.ON_PAUSE
) {
destroy { customActionForDestroy?.invoke() }
}
}
}
@@ -88,7 +88,8 @@ object ImageSaver {
val client = HttpClientManager.getHttpClient()
val request =
Request.Builder()
Request
.Builder()
.header("User-Agent", "Amethyst/${BuildConfig.VERSION_NAME}")
.get()
.url(url)
@@ -35,12 +35,15 @@ import com.vitorpamplona.quartz.encoders.decodePublicKey
import com.vitorpamplona.quartz.encoders.toHexKey
import kotlin.coroutines.cancellation.CancellationException
data class RangesChanges(val original: TextRange, val modified: TextRange)
data class RangesChanges(
val original: TextRange,
val modified: TextRange,
)
class UrlUserTagTransformation(val color: Color) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
return buildAnnotatedStringWithUrlHighlighting(text, color)
}
class UrlUserTagTransformation(
val color: Color,
) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText = buildAnnotatedStringWithUrlHighlighting(text, color)
}
fun buildAnnotatedStringWithUrlHighlighting(
@@ -78,12 +78,15 @@ abstract class BasicRelaySetupInfoModel : ViewModel() {
_relays.update {
val relayList = getRelayList() ?: emptyList()
relayList.map { relayUrl ->
BasicRelaySetupInfo(
RelayUrlFormatter.normalize(relayUrl),
RelayStats.get(relayUrl),
)
}.distinctBy { it.url }.sortedBy { it.relayStat.receivedBytes }.reversed()
relayList
.map { relayUrl ->
BasicRelaySetupInfo(
RelayUrlFormatter.normalize(relayUrl),
RelayStats.get(relayUrl),
)
}.distinctBy { it.url }
.sortedBy { it.relayStat.receivedBytes }
.reversed()
}
}
@@ -21,9 +21,7 @@
package com.vitorpamplona.amethyst.ui.actions.relays
class DMRelayListViewModel : BasicRelaySetupInfoModel() {
override fun getRelayList(): List<String>? {
return account.getDMRelayList()?.relays()
}
override fun getRelayList(): List<String>? = account.getDMRelayList()?.relays()
override fun saveRelayList(urlList: List<String>) {
account.saveDMRelayList(urlList)
@@ -21,9 +21,7 @@
package com.vitorpamplona.amethyst.ui.actions.relays
class LocalRelayListViewModel : BasicRelaySetupInfoModel() {
override fun getRelayList(): List<String>? {
return account.localRelayServers.toList()
}
override fun getRelayList(): List<String>? = account.localRelayServers.toList()
override fun saveRelayList(urlList: List<String>) {
account.updateLocalRelayServers(urlList.toSet())
@@ -105,23 +105,29 @@ class Nip65RelayListViewModel : ViewModel() {
_homeRelays.update {
val relayList = account.getNIP65RelayList()?.writeRelays() ?: emptyList()
relayList.map { relayUrl ->
BasicRelaySetupInfo(
RelayUrlFormatter.normalize(relayUrl),
RelayStats.get(relayUrl),
)
}.distinctBy { it.url }.sortedBy { it.relayStat.receivedBytes }.reversed()
relayList
.map { relayUrl ->
BasicRelaySetupInfo(
RelayUrlFormatter.normalize(relayUrl),
RelayStats.get(relayUrl),
)
}.distinctBy { it.url }
.sortedBy { it.relayStat.receivedBytes }
.reversed()
}
_notificationRelays.update {
val relayList = account.getNIP65RelayList()?.readRelays() ?: emptyList()
relayList.map { relayUrl ->
BasicRelaySetupInfo(
RelayUrlFormatter.normalize(relayUrl),
RelayStats.get(relayUrl),
)
}.distinctBy { it.url }.sortedBy { it.relayStat.receivedBytes }.reversed()
relayList
.map { relayUrl ->
BasicRelaySetupInfo(
RelayUrlFormatter.normalize(relayUrl),
RelayStats.get(relayUrl),
)
}.distinctBy { it.url }
.sortedBy { it.relayStat.receivedBytes }
.reversed()
}
}
@@ -21,9 +21,7 @@
package com.vitorpamplona.amethyst.ui.actions.relays
class PrivateOutboxRelayListViewModel : BasicRelaySetupInfoModel() {
override fun getRelayList(): List<String>? {
return account.getPrivateOutboxRelayList()?.relays()
}
override fun getRelayList(): List<String>? = account.getPrivateOutboxRelayList()?.relays()
override fun saveRelayList(urlList: List<String>) {
account.savePrivateOutboxRelayList(urlList)
@@ -21,9 +21,7 @@
package com.vitorpamplona.amethyst.ui.actions.relays
class SearchRelayListViewModel : BasicRelaySetupInfoModel() {
override fun getRelayList(): List<String>? {
return account.getSearchRelayList()?.relays()
}
override fun getRelayList(): List<String>? = account.getSearchRelayList()?.relays()
override fun saveRelayList(urlList: List<String>) {
account.saveSearchRelayList(urlList)
@@ -101,11 +101,11 @@ fun AudioWaveformReadOnly(
minHeight = MIN_SPIKE_HEIGHT,
maxHeight = canvasSize.height.coerceAtLeast(MIN_SPIKE_HEIGHT),
)
}
.map { animateFloatAsState(it, spikeAnimationSpec).value }
}.map { animateFloatAsState(it, spikeAnimationSpec).value }
Canvas(
modifier =
Modifier.fillMaxWidth()
Modifier
.fillMaxWidth()
.requiredHeight(48.dp)
.graphicsLayer(alpha = DEFAULT_GRAPHICS_LAYER_ALPHA)
.then(modifier),
@@ -161,8 +161,7 @@ private fun List<Int>.toDrawableAmplitudes(
AmplitudeType.Avg -> data.average()
AmplitudeType.Max -> data.max()
AmplitudeType.Min -> data.min()
}
.toFloat()
}.toFloat()
.coerceIn(minHeight, maxHeight)
}
return when {
@@ -198,9 +197,7 @@ internal fun <T> Iterable<T>.chunkToSize(
internal fun Iterable<Float>.normalize(
min: Float,
max: Float,
): List<Float> {
return map { (max - min) * ((it - min()) / (max() - min())) + min }
}
): List<Float> = map { (max - min) * ((it - min()) / (max() - min())) + min }
private fun Int.safeDiv(value: Int): Float {
return if (value == 0) return 0F else this / value.toFloat()
@@ -26,9 +26,13 @@ import androidx.compose.runtime.Immutable
sealed class GenericLoadable<T> {
@Immutable class Loading<T> : GenericLoadable<T>()
@Immutable class Loaded<T>(val loaded: T) : GenericLoadable<T>()
@Immutable class Loaded<T>(
val loaded: T,
) : GenericLoadable<T>()
@Immutable class Empty<T> : GenericLoadable<T>()
@Immutable class Error<T>(val errorMessage: String) : GenericLoadable<T>()
@Immutable class Error<T>(
val errorMessage: String,
) : GenericLoadable<T>()
}
@@ -25,12 +25,14 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import kotlin.math.abs
class SplitItem<T>(val key: T) {
class SplitItem<T>(
val key: T,
) {
// 0 to 1
var percentage by mutableStateOf(0f)
}
class Split<T>() {
class Split<T> {
var items: List<SplitItem<T>> by mutableStateOf(emptyList())
fun addItem(
@@ -27,9 +27,13 @@ import com.vitorpamplona.amethyst.service.previews.UrlInfoItem
sealed class UrlPreviewState {
@Immutable object Loading : UrlPreviewState()
@Immutable class Loaded(val previewInfo: UrlInfoItem) : UrlPreviewState()
@Immutable class Loaded(
val previewInfo: UrlInfoItem,
) : UrlPreviewState()
@Immutable object Empty : UrlPreviewState()
@Immutable class Error(val errorMessage: String) : UrlPreviewState()
@Immutable class Error(
val errorMessage: String,
) : UrlPreviewState()
}
@@ -24,10 +24,10 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
class BookmarkPrivateFeedFilter(val account: Account) : FeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().latestBookmarkList?.id ?: ""
}
class BookmarkPrivateFeedFilter(
val account: Account,
) : FeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().latestBookmarkList?.id ?: ""
override fun feed(): List<Note> {
val bookmarks = account.userProfile().latestBookmarkList
@@ -24,10 +24,10 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
class BookmarkPublicFeedFilter(val account: Account) : FeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().latestBookmarkList?.id ?: ""
}
class BookmarkPublicFeedFilter(
val account: Account,
) : FeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().latestBookmarkList?.id ?: ""
override fun feed(): List<Note> {
val bookmarks = account.userProfile().latestBookmarkList
@@ -24,27 +24,24 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.Note
class ChannelFeedFilter(val channel: Channel, val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return channel.idHex
}
class ChannelFeedFilter(
val channel: Channel,
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = channel.idHex
// returns the last Note of each user.
override fun feed(): List<Note> {
return sort(
override fun feed(): List<Note> =
sort(
channel.notes.filterIntoSet { key, it ->
account.isAcceptable(it)
},
)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return collection
override fun applyFilter(collection: Set<Note>): Set<Note> =
collection
.filter { channel.notes.containsKey(it.idHex) && account.isAcceptable(it) }
.toSet()
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
}
@@ -24,12 +24,12 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.events.ChatroomKey
class ChatroomFeedFilter(val withUser: ChatroomKey, val account: Account) :
AdditiveFeedFilter<Note>() {
class ChatroomFeedFilter(
val withUser: ChatroomKey,
val account: Account,
) : AdditiveFeedFilter<Note>() {
// returns the last Note of each user.
override fun feedKey(): String {
return withUser.hashCode().toString()
}
override fun feedKey(): String = withUser.hashCode().toString()
override fun feed(): List<Note> {
val messages = account.userProfile().privateChatrooms[withUser] ?: return emptyList()
@@ -46,7 +46,5 @@ class ChatroomFeedFilter(val withUser: ChatroomKey, val account: Account) :
return collection.filter { it in messages.roomMessages && account.isAcceptable(it) }.toSet()
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
}
@@ -28,10 +28,10 @@ import com.vitorpamplona.quartz.events.ChannelMessageEvent
import com.vitorpamplona.quartz.events.ChatroomKey
import com.vitorpamplona.quartz.events.ChatroomKeyable
class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex
}
class ChatroomListKnownFeedFilter(
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex
// returns the last Note of each user.
override fun feed(): List<Note> {
@@ -56,7 +56,8 @@ class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter<Not
.selectedChatsFollowList()
.mapNotNull { LocalCache.getChannelIfExists(it) }
.mapNotNull { it ->
it.notes.filter { key, it -> account.isAcceptable(it) && it.event != null }
it.notes
.filter { key, it -> account.isAcceptable(it) && it.event != null }
.sortedWith(DefaultFeedOrder)
.firstOrNull()
}
@@ -136,7 +137,11 @@ class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter<Not
account: Account,
): MutableMap<String, Note> {
val followingChannels =
account.userProfile().latestContactList?.taggedEvents()?.toSet() ?: emptySet()
account
.userProfile()
.latestContactList
?.taggedEvents()
?.toSet() ?: emptySet()
val newRelevantPublicMessages = mutableMapOf<String, Note>()
newItems
.filter { it.event is ChannelMessageEvent }
@@ -177,7 +182,8 @@ class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter<Not
newNote.author?.pubkeyHex == me.pubkeyHex ||
room.senderIntersects(followingKeySet) ||
me.hasSentMessagesTo(roomKey)
) && !account.isAllHidden(roomKey.users)
) &&
!account.isAllHidden(roomKey.users)
) {
val lastNote = newRelevantPrivateMessages.get(roomKey)
if (lastNote != null) {
@@ -193,7 +199,5 @@ class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter<Not
return newRelevantPrivateMessages
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
}
@@ -27,10 +27,10 @@ import com.vitorpamplona.quartz.events.ChatroomKey
import com.vitorpamplona.quartz.events.ChatroomKeyable
import com.vitorpamplona.quartz.events.PrivateDmEvent
class ChatroomListNewFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex
}
class ChatroomListNewFeedFilter(
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex
// returns the last Note of each user.
override fun feed(): List<Note> {
@@ -137,7 +137,5 @@ class ChatroomListNewFeedFilter(val account: Account) : AdditiveFeedFilter<Note>
return newRelevantPrivateMessages
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
}
@@ -27,10 +27,11 @@ 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>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + note.idHex
}
class CommunityFeedFilter(
val note: AddressableNote,
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + note.idHex
override fun feed(): List<Note> {
val myPubKey = account.userProfile().pubkeyHex
@@ -42,23 +43,23 @@ class CommunityFeedFilter(val note: AddressableNote, val account: Account) : Add
return sort(result)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val myPubKey = account.userProfile().pubkeyHex
return collection.mapNotNull {
filterMap(it, myPubKey)
}.flatten().toSet()
return collection
.mapNotNull {
filterMap(it, myPubKey)
}.flatten()
.toSet()
}
private fun filterMap(
note: Note,
myPubKey: HexKey,
): List<Note>? {
return if (
): List<Note>? =
if (
// Only Approvals
note.event is CommunityPostApprovalEvent &&
// Of the given community
@@ -74,9 +75,6 @@ class CommunityFeedFilter(val note: AddressableNote, val account: Account) : Add
} else {
null
}
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
}
@@ -29,17 +29,16 @@ import com.vitorpamplona.quartz.events.IsInPublicChatChannel
import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + account.defaultDiscoveryFollowList.value
}
open class DiscoverChatFeedFilter(
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.defaultDiscoveryFollowList.value
override fun showHiddenKey(): Boolean {
return account.defaultDiscoveryFollowList.value ==
override fun showHiddenKey(): Boolean =
account.defaultDiscoveryFollowList.value ==
PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultDiscoveryFollowList.value ==
MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
}
override fun feed(): List<Note> {
val params = buildFilterParams(account)
@@ -63,18 +62,15 @@ open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Not
return sort(allChannelNotes)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
fun buildFilterParams(account: Account): FilterByListParams =
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 params = buildFilterParams(account)
@@ -120,7 +116,6 @@ open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Not
{ it.createdAt() },
{ it.idHex },
),
)
.reversed()
).reversed()
}
}
@@ -29,17 +29,16 @@ import com.vitorpamplona.quartz.events.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.events.MuteListEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + account.defaultDiscoveryFollowList.value
}
open class DiscoverCommunityFeedFilter(
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.defaultDiscoveryFollowList.value
override fun showHiddenKey(): Boolean {
return account.defaultDiscoveryFollowList.value ==
override fun showHiddenKey(): Boolean =
account.defaultDiscoveryFollowList.value ==
PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultDiscoveryFollowList.value ==
MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
}
override fun feed(): List<Note> {
val filterParams =
@@ -67,9 +66,7 @@ open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilte
return sort(notes)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
// here, we need to look for CommunityDefinition in new collection AND new CommunityDefinition from Post Approvals
@@ -81,28 +78,30 @@ open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilte
hiddenUsers = account.flowHiddenUsers.value,
)
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
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
if (definitionEvent == null && shouldInclude(it, filterParams)) {
definitionNote
} else if (definitionEvent is CommunityDefinitionEvent && filterParams.match(definitionEvent)) {
definitionNote
} else {
null
if (definitionEvent == null && shouldInclude(it, filterParams)) {
definitionNote
} else if (definitionEvent is CommunityDefinitionEvent && filterParams.match(definitionEvent)) {
definitionNote
} else {
null
}
}
} else {
null
}
} else {
null
}
}.flatten().toSet()
}.flatten()
.toSet()
}
private fun shouldInclude(
@@ -123,7 +122,6 @@ open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilte
{ it.createdAt() },
{ it.idHex },
),
)
.reversed()
).reversed()
}
}
@@ -34,18 +34,13 @@ import com.vitorpamplona.quartz.events.PeopleListEvent
open class DiscoverLiveFeedFilter(
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + followList()
}
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList()
open fun followList(): String {
return account.defaultDiscoveryFollowList.value
}
open fun followList(): String = account.defaultDiscoveryFollowList.value
override fun showHiddenKey(): Boolean {
return followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
override fun showHiddenKey(): Boolean =
followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
}
override fun feed(): List<Note> {
val allChannelNotes = LocalCache.channels.mapNotNull { _, channel -> LocalCache.getNoteIfExists(channel.idHex) }
@@ -56,9 +51,7 @@ open class DiscoverLiveFeedFilter(
return sort(notes)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val filterParams =
@@ -92,16 +85,14 @@ open class DiscoverLiveFeedFilter(
{ (it.event as? LiveActivitiesEvent)?.starts() ?: it.createdAt() },
{ it.idHex },
),
)
.reversed()
).reversed()
}
fun convertStatusToOrder(status: String?): Int {
return when (status) {
fun convertStatusToOrder(status: String?): Int =
when (status) {
STATUS_LIVE -> 2
STATUS_PLANNED -> 1
STATUS_ENDED -> 0
else -> 0
}
}
}
@@ -30,18 +30,13 @@ import com.vitorpamplona.quartz.events.PeopleListEvent
open class DiscoverMarketplaceFeedFilter(
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + followList()
}
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList()
open fun followList(): String {
return account.defaultDiscoveryFollowList.value
}
open fun followList(): String = account.defaultDiscoveryFollowList.value
override fun showHiddenKey(): Boolean {
return followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
override fun showHiddenKey(): Boolean =
followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
}
override fun feed(): List<Note> {
val params = buildFilterParams(account)
@@ -55,18 +50,15 @@ open class DiscoverMarketplaceFeedFilter(
return sort(notes)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.create(
account.userProfile().pubkeyHex,
account.defaultDiscoveryFollowList.value,
account.liveDiscoveryFollowLists.value,
account.flowHiddenUsers.value,
)
}
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val params = buildFilterParams(account)
@@ -77,7 +69,5 @@ open class DiscoverMarketplaceFeedFilter(
}
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
}
@@ -34,18 +34,13 @@ open class DiscoverNIP89FeedFilter(
val lastAnnounced = 90 * 24 * 60 * 60 // 90 Days ago
// TODO better than announced would be last active, as this requires the DVM provider to regularly update the NIP89 announcement
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + followList()
}
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + followList()
open fun followList(): String {
return account.defaultDiscoveryFollowList.value
}
open fun followList(): String = account.defaultDiscoveryFollowList.value
override fun showHiddenKey(): Boolean {
return followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
override fun showHiddenKey(): Boolean =
followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
}
override fun feed(): List<Note> {
val filterParams = buildFilterParams(account)
@@ -58,18 +53,15 @@ open class DiscoverNIP89FeedFilter(
return sort(notes)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.create(
account.userProfile().pubkeyHex,
account.defaultDiscoveryFollowList.value,
account.liveDiscoveryFollowLists.value,
account.flowHiddenUsers.value,
)
}
fun acceptDVM(note: Note): Boolean {
val noteEvent = note.event
@@ -88,13 +80,10 @@ open class DiscoverNIP89FeedFilter(
noteEvent.createdAt > TimeUtils.now() - lastAnnounced // && params.match(noteEvent)
}
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
return collection.filterTo(HashSet()) {
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> =
collection.filterTo(HashSet()) {
acceptDVM(it)
}
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(compareBy({ it.createdAt() }, { it.idHex })).reversed()
}
@@ -25,16 +25,15 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.quartz.events.DraftEvent
class DraftEventsFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex
}
class DraftEventsFeedFilter(
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex
override fun applyFilter(collection: Set<Note>): Set<Note> {
return collection.filterTo(HashSet()) {
override fun applyFilter(collection: Set<Note>): Set<Note> =
collection.filterTo(HashSet()) {
acceptableEvent(it)
}
}
override fun feed(): List<Note> {
val drafts =
@@ -50,7 +49,5 @@ class DraftEventsFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
return noteEvent is DraftEvent && noteEvent.pubKey == account.userProfile().pubkeyHex
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
}
@@ -91,13 +91,12 @@ class FilterByListParams(
selectedListName: String,
followLists: Account.LiveFollowLists?,
hiddenUsers: Account.LiveHiddenUsers,
): FilterByListParams {
return FilterByListParams(
): FilterByListParams =
FilterByListParams(
isGlobal = selectedListName == GLOBAL_FOLLOWS,
isHiddenList = showHiddenKey(selectedListName, userHex),
followLists = followLists,
hiddenLists = hiddenUsers,
)
}
}
}
@@ -31,10 +31,11 @@ import com.vitorpamplona.quartz.events.PrivateDmEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.events.WikiNoteEvent
class GeoHashFeedFilter(val tag: String, val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + tag
}
class GeoHashFeedFilter(
val tag: String,
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + tag
override fun feed(): List<Note> {
val notes =
@@ -45,19 +46,15 @@ class GeoHashFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
return sort(notes)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
return collection.filterTo(HashSet<Note>()) { acceptableEvent(it, tag) }
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> = collection.filterTo(HashSet<Note>()) { acceptableEvent(it, tag) }
fun acceptableEvent(
it: Note,
geoTag: String,
): Boolean {
return (
): Boolean =
(
it.event is TextNoteEvent ||
it.event is LongTextNoteEvent ||
it.event is WikiNoteEvent ||
@@ -66,9 +63,6 @@ class GeoHashFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
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(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
}
@@ -33,10 +33,11 @@ import com.vitorpamplona.quartz.events.RepostEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.events.WikiNoteEvent
class HashtagFeedFilter(val tag: String, val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + tag
}
class HashtagFeedFilter(
val tag: String,
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + tag
override fun feed(): List<Note> {
val notes =
@@ -47,19 +48,15 @@ class HashtagFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
return sort(notes)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
return collection.filterTo(HashSet<Note>()) { acceptableEvent(it, tag) }
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> = collection.filterTo(HashSet<Note>()) { acceptableEvent(it, tag) }
fun acceptableEvent(
it: Note,
hashTag: String,
): Boolean {
return (
): Boolean =
(
it.event is TextNoteEvent ||
it.event is RepostEvent ||
it.event is GenericRepostEvent ||
@@ -69,10 +66,9 @@ class HashtagFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
it.event is PrivateDmEvent ||
it.event is PollNoteEvent ||
it.event is AudioHeaderEvent
) && it.event?.isTaggedHash(hashTag) == true && account.isAcceptable(it)
}
) &&
it.event?.isTaggedHash(hashTag) == true &&
account.isAcceptable(it)
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
}
@@ -30,15 +30,14 @@ import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + account.defaultHomeFollowList.value
}
class HomeConversationsFeedFilter(
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.defaultHomeFollowList.value
override fun showHiddenKey(): Boolean {
return account.defaultHomeFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
override fun showHiddenKey(): Boolean =
account.defaultHomeFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultHomeFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
}
override fun feed(): List<Note> {
val filterParams = buildFilterParams(account)
@@ -50,18 +49,15 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Not
)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
fun buildFilterParams(account: Account): FilterByListParams =
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 filterParams = buildFilterParams(account)
@@ -74,16 +70,15 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Not
fun acceptableEvent(
it: Note,
filterParams: FilterByListParams,
): Boolean {
return (
): Boolean =
(
it.event is TextNoteEvent ||
it.event is PollNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is LiveActivitiesChatMessageEvent
) && filterParams.match(it.event) && !it.isNewThread()
}
) &&
filterParams.match(it.event) &&
!it.isNewThread()
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
}
@@ -36,24 +36,22 @@ import com.vitorpamplona.quartz.events.RepostEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.events.WikiNoteEvent
class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + account.defaultHomeFollowList.value
}
class HomeNewThreadFeedFilter(
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.defaultHomeFollowList.value
override fun showHiddenKey(): Boolean {
return account.defaultHomeFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
override fun showHiddenKey(): Boolean =
account.defaultHomeFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultHomeFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
}
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.create(
userHex = account.userProfile().pubkeyHex,
selectedListName = account.defaultHomeFollowList.value,
followLists = account.liveHomeFollowLists.value,
hiddenUsers = account.flowHiddenUsers.value,
)
}
override fun feed(): List<Note> {
val gRelays = account.activeGlobalRelays().toSet()
@@ -73,9 +71,7 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
return sort(notes + longFormNotes)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val gRelays = account.activeGlobalRelays().toSet()
@@ -107,13 +103,13 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
) && filterParams.match(noteEvent, isGlobalRelay) && it.isNewThread()
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.distinctBy {
if (it.event is RepostEvent || it.event is GenericRepostEvent) {
it.replyTo?.lastOrNull()?.idHex ?: it.idHex // only the most recent repost per feed.
} else {
it.idHex
}
}.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> =
collection
.distinctBy {
if (it.event is RepostEvent || it.event is GenericRepostEvent) {
it.replyTo?.lastOrNull()?.idHex ?: it.idHex // only the most recent repost per feed.
} else {
it.idHex
}
}.sortedWith(DefaultFeedOrder)
}
@@ -35,18 +35,13 @@ open class NIP90ContentDiscoveryResponseFilter(
) : AdditiveFeedFilter<Note>() {
var latestNote: Note? = null
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + request
}
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + request
open fun followList(): String {
return account.defaultDiscoveryFollowList.value
}
open fun followList(): String = account.defaultDiscoveryFollowList.value
override fun showHiddenKey(): Boolean {
return followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
override fun showHiddenKey(): Boolean =
followList() == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
followList() == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
}
fun acceptableEvent(note: Note): Boolean {
val noteEvent = note.event
@@ -71,18 +66,15 @@ open class NIP90ContentDiscoveryResponseFilter(
}
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.create(
account.userProfile().pubkeyHex,
account.defaultDiscoveryFollowList.value,
account.liveDiscoveryFollowLists.value,
account.flowHiddenUsers.value,
)
}
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
// val params = buildFilterParams(account)
@@ -95,9 +87,11 @@ open class NIP90ContentDiscoveryResponseFilter(
val noteEvent = latestNote?.event as? NIP90ContentDiscoveryResponseEvent ?: return setOf()
return noteEvent.innerTags().mapNotNull {
LocalCache.checkGetOrCreateNote(it)
}.toSet()
return noteEvent
.innerTags()
.mapNotNull {
LocalCache.checkGetOrCreateNote(it)
}.toSet()
}
override fun sort(collection: Set<Note>): List<Note> {
@@ -44,26 +44,24 @@ import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.events.ReactionEvent
import com.vitorpamplona.quartz.events.RepostEvent
class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + account.defaultNotificationFollowList.value
}
class NotificationFeedFilter(
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.defaultNotificationFollowList.value
override fun showHiddenKey(): Boolean {
return account.defaultNotificationFollowList.value ==
override fun showHiddenKey(): Boolean =
account.defaultNotificationFollowList.value ==
PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultNotificationFollowList.value ==
MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
}
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
fun buildFilterParams(account: Account): FilterByListParams =
FilterByListParams.create(
userHex = account.userProfile().pubkeyHex,
selectedListName = account.defaultNotificationFollowList.value,
followLists = account.liveNotificationFollowLists.value,
hiddenUsers = account.flowHiddenUsers.value,
)
}
override fun feed(): List<Note> {
val filterParams = buildFilterParams(account)
@@ -76,9 +74,7 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
return sort(notifications)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val filterParams = buildFilterParams(account)
@@ -123,9 +119,7 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
tagsAnEventByUser(it, loggedInUserHex)
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
fun tagsAnEventByUser(
note: Note,
@@ -153,11 +147,17 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
}
if (event is ReactionEvent) {
return note.replyTo?.lastOrNull()?.author?.pubkeyHex == authorHex
return note.replyTo
?.lastOrNull()
?.author
?.pubkeyHex == authorHex
}
if (event is RepostEvent || event is GenericRepostEvent) {
return note.replyTo?.lastOrNull()?.author?.pubkeyHex == authorHex
return note.replyTo
?.lastOrNull()
?.author
?.pubkeyHex == authorHex
}
return true
@@ -29,10 +29,11 @@ import com.vitorpamplona.amethyst.model.ThreadLevelCalculator
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
class ThreadFeedFilter(val account: Account, val noteId: String) : FeedFilter<Note>() {
override fun feedKey(): String {
return noteId
}
class ThreadFeedFilter(
val account: Account,
val noteId: String,
) : FeedFilter<Note>() {
override fun feedKey(): String = noteId
override fun feed(): List<Note> {
val cachedSignatures: MutableMap<Note, LevelSignature> = mutableMapOf()
@@ -44,14 +45,15 @@ class ThreadFeedFilter(val account: Account, val noteId: String) : FeedFilter<No
// Currently orders by date of each event, descending, at each level of the reply stack
val order =
compareByDescending<Note> {
ThreadLevelCalculator.replyLevelSignature(
it,
eventsInHex,
cachedSignatures,
account.userProfile(),
followingKeySet,
now,
).signature
ThreadLevelCalculator
.replyLevelSignature(
it,
eventsInHex,
cachedSignatures,
account.userProfile(),
followingKeySet,
now,
).signature
}
return eventsToWatch.sortedWith(order)
@@ -25,10 +25,10 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.events.AppRecommendationEvent
class UserProfileAppRecommendationsFeedFilter(val user: User) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return user.pubkeyHex
}
class UserProfileAppRecommendationsFeedFilter(
val user: User,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = user.pubkeyHex
override fun feed(): List<Note> {
val recommendations =
@@ -39,13 +39,9 @@ class UserProfileAppRecommendationsFeedFilter(val user: User) : AdditiveFeedFilt
return sort(recommendations)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
return collection.mapNotNull { filterMap(it) }.flatten().toSet()
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> = collection.mapNotNull { filterMap(it) }.flatten().toSet()
fun filterMap(it: Note): List<Note>? {
val noteEvent = it.event
@@ -58,7 +54,5 @@ class UserProfileAppRecommendationsFeedFilter(val user: User) : AdditiveFeedFilt
return null
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
}
@@ -25,10 +25,11 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
class UserProfileBookmarksFeedFilter(val user: User, val account: Account) : FeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + user.pubkeyHex
}
class UserProfileBookmarksFeedFilter(
val user: User,
val account: Account,
) : FeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + user.pubkeyHex
override fun feed(): List<Note> {
val notes =
@@ -29,11 +29,11 @@ import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
class UserProfileConversationsFeedFilter(val user: User, val account: Account) :
AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + user.pubkeyHex
}
class UserProfileConversationsFeedFilter(
val user: User,
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + user.pubkeyHex
override fun feed(): List<Note> {
val notes =
@@ -49,27 +49,22 @@ class UserProfileConversationsFeedFilter(val user: User, val account: Account) :
return sort(notes + longFormNotes)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
return collection.filterTo(HashSet()) { acceptableEvent(it) }
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> = collection.filterTo(HashSet()) { acceptableEvent(it) }
fun acceptableEvent(it: Note): Boolean {
return it.author == user &&
fun acceptableEvent(it: Note): Boolean =
it.author == user &&
(
it.event is TextNoteEvent ||
it.event is PollNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is LiveActivitiesChatMessageEvent
) && !it.isNewThread() && account.isAcceptable(it)
}
) &&
!it.isNewThread() &&
account.isAcceptable(it)
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
override fun limit() = 200
}
@@ -24,16 +24,16 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
class UserProfileFollowersFeedFilter(val user: User, val account: Account) : FeedFilter<User>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + user.pubkeyHex
}
class UserProfileFollowersFeedFilter(
val user: User,
val account: Account,
) : FeedFilter<User>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + user.pubkeyHex
override fun feed(): List<User> {
return LocalCache.users.filter { _, it ->
override fun feed(): List<User> =
LocalCache.users.filter { _, it ->
it.isFollowing(user) && !account.isHidden(it)
}
}
override fun limit() = 400
}
@@ -25,10 +25,11 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.events.ContactListEvent
class UserProfileFollowsFeedFilter(val user: User, val account: Account) : FeedFilter<User>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + user.pubkeyHex
}
class UserProfileFollowsFeedFilter(
val user: User,
val account: Account,
) : FeedFilter<User>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + user.pubkeyHex
val cache: MutableMap<ContactListEvent, List<User>> = mutableMapOf()
@@ -37,11 +37,11 @@ import com.vitorpamplona.quartz.events.RepostEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.events.WikiNoteEvent
class UserProfileNewThreadFeedFilter(val user: User, val account: Account) :
AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + user.pubkeyHex
}
class UserProfileNewThreadFeedFilter(
val user: User,
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + user.pubkeyHex
override fun feed(): List<Note> {
val notes =
@@ -57,16 +57,12 @@ class UserProfileNewThreadFeedFilter(val user: User, val account: Account) :
return sort(notes + longFormNotes)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
return collection.filterTo(HashSet()) { acceptableEvent(it) }
}
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> = collection.filterTo(HashSet()) { acceptableEvent(it) }
fun acceptableEvent(it: Note): Boolean {
return it.author == user &&
fun acceptableEvent(it: Note): Boolean =
it.author == user &&
(
it.event is TextNoteEvent ||
it.event is ClassifiedsEvent ||
@@ -79,11 +75,8 @@ class UserProfileNewThreadFeedFilter(val user: User, val account: Account) :
it.event is AudioTrackEvent ||
it.event is AudioHeaderEvent
) && it.isNewThread() && account.isAcceptable(it)
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
override fun limit() = 200
}
@@ -24,28 +24,21 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.events.ReportEvent
class UserProfileReportsFeedFilter(val user: User) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return user.pubkeyHex
}
class UserProfileReportsFeedFilter(
val user: User,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = user.pubkeyHex
override fun feed(): List<Note> {
return sort(innerApplyFilter(user.reports.values.flatten()))
}
override fun feed(): List<Note> = sort(innerApplyFilter(user.reports.values.flatten()))
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
return collection
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> =
collection
.filter { it.event is ReportEvent && it.event?.isTaggedUser(user.pubkeyHex) == true }
.toSet()
}
override fun sort(collection: Set<Note>): List<Note> {
return collection.sortedWith(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
override fun limit() = 400
}
@@ -24,14 +24,12 @@ import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.screen.ZapReqResponse
import com.vitorpamplona.quartz.events.zaps.UserZaps
class UserProfileZapsFeedFilter(val user: User) : FeedFilter<ZapReqResponse>() {
override fun feedKey(): String {
return user.pubkeyHex
}
class UserProfileZapsFeedFilter(
val user: User,
) : FeedFilter<ZapReqResponse>() {
override fun feedKey(): String = user.pubkeyHex
override fun feed(): List<ZapReqResponse> {
return UserZaps.forProfileFeed(user.zaps)
}
override fun feed(): List<ZapReqResponse> = UserZaps.forProfileFeed(user.zaps)
override fun limit() = 400
}
@@ -31,15 +31,14 @@ import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
import com.vitorpamplona.quartz.events.VideoVerticalEvent
class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String {
return account.userProfile().pubkeyHex + "-" + account.defaultStoriesFollowList.value
}
class VideoFeedFilter(
val account: Account,
) : AdditiveFeedFilter<Note>() {
override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + account.defaultStoriesFollowList.value
override fun showHiddenKey(): Boolean {
return account.defaultStoriesFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
override fun showHiddenKey(): Boolean =
account.defaultStoriesFollowList.value == PeopleListEvent.blockListFor(account.userProfile().pubkeyHex) ||
account.defaultStoriesFollowList.value == MuteListEvent.blockListFor(account.userProfile().pubkeyHex)
}
override fun feed(): List<Note> {
val params = buildFilterParams(account)
@@ -52,9 +51,7 @@ class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
return sort(notes)
}
override fun applyFilter(collection: Set<Note>): Set<Note> {
return innerApplyFilter(collection)
}
override fun applyFilter(collection: Set<Note>): Set<Note> = innerApplyFilter(collection)
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
val params = buildFilterParams(account)
@@ -78,16 +75,13 @@ class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
account.isAcceptable(it)
}
fun buildFilterParams(account: Account): FilterByListParams {
return FilterByListParams.create(
fun buildFilterParams(account: Account): FilterByListParams =
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(DefaultFeedOrder)
}
override fun sort(collection: Set<Note>): List<Note> = collection.sortedWith(DefaultFeedOrder)
}
@@ -385,7 +385,12 @@ fun AppNavigation(
val activity = LocalContext.current.getActivity()
var currentIntentNextPage by remember {
mutableStateOf(activity.intent?.data?.toString()?.ifBlank { null })
mutableStateOf(
activity.intent
?.data
?.toString()
?.ifBlank { null },
)
}
currentIntentNextPage?.let { intentNextPage ->
@@ -126,18 +126,10 @@ fun routeToMessage(
user: User,
draftMessage: String?,
accountViewModel: AccountViewModel,
): String {
return routeToMessage(user.pubkeyHex, draftMessage, accountViewModel)
}
): String = routeToMessage(user.pubkeyHex, draftMessage, accountViewModel)
fun routeFor(note: Channel): String {
return "Channel/${note.idHex}"
}
fun routeFor(note: Channel): String = "Channel/${note.idHex}"
fun routeFor(user: User): String {
return "User/${user.pubkeyHex}"
}
fun routeFor(user: User): String = "User/${user.pubkeyHex}"
fun authorRouteFor(note: Note): String {
return "User/${note.author?.pubkeyHex}"
}
fun authorRouteFor(note: Note): String = "User/${note.author?.pubkeyHex}"
@@ -206,7 +206,8 @@ fun LoadCityName(
val geoHash = runCatching { geohashStr.toGeoHash() }.getOrNull()
if (geoHash != null) {
val newCityName =
CachedGeoLocations.geoLocate(geohashStr, geoHash.toLocation(), context)
CachedGeoLocations
.geoLocate(geohashStr, geoHash.toLocation(), context)
?.ifBlank { null }
if (newCityName != null && newCityName != cityName) {
cityName = newCityName
@@ -86,7 +86,11 @@ class PollNoteViewModel : ViewModel() {
valueMinimumBD = valueMinimum?.let { BigDecimal(it) }
valueMaximumBD = valueMaximum?.let { BigDecimal(it) }
consensusThreshold =
pollEvent?.getTagLong(CONSENSUS_THRESHOLD)?.toFloat()?.div(100)?.toBigDecimal()
pollEvent
?.getTagLong(CONSENSUS_THRESHOLD)
?.toFloat()
?.div(100)
?.toBigDecimal()
closedAt = pollEvent?.getTagLong(CLOSED_AT)
totalZapped = BigDecimal.ZERO
@@ -138,7 +142,8 @@ class PollNoteViewModel : ViewModel() {
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
fun isPollClosed(): Boolean =
closedAt?.let { // allow 2 minute leeway for zap to propagate
closedAt?.let {
// allow 2 minute leeway for zap to propagate
pollNote?.createdAt()?.plus(it * (86400 + 120))!! < TimeUtils.now()
} == true
@@ -221,17 +226,16 @@ class PollNoteViewModel : ViewModel() {
fun cachedIsPollOptionZappedBy(
option: Int,
user: User,
): Boolean {
return pollNote!!.zaps.any {
): Boolean =
pollNote!!.zaps.any {
val zapEvent = it.value?.event as? LnZapEvent
val privateZapAuthor = (it.key.event as? LnZapRequestEvent)?.cachedPrivateZap()
zapEvent?.zappedPollOption() == option &&
(it.key.author?.pubkeyHex == user.pubkeyHex || privateZapAuthor?.pubKey == user.pubkeyHex)
}
}
private fun zappedPollOptionAmount(option: Int): BigDecimal {
return pollNote?.zaps?.values?.sumOf {
private fun zappedPollOptionAmount(option: Int): BigDecimal =
pollNote?.zaps?.values?.sumOf {
val event = it?.event as? LnZapEvent
val zapAmount = event?.amount ?: BigDecimal.ZERO
val isValidAmount = isValidInputVoteAmount(event?.amount)
@@ -243,10 +247,9 @@ class PollNoteViewModel : ViewModel() {
}
}
?: BigDecimal.ZERO
}
private fun totalZapped(): BigDecimal {
return pollNote?.zaps?.values?.sumOf {
private fun totalZapped(): BigDecimal =
pollNote?.zaps?.values?.sumOf {
val zapEvent = (it?.event as? LnZapEvent)
val zapAmount = zapEvent?.amount ?: BigDecimal.ZERO
val isValidAmount = isValidInputVoteAmount(zapEvent?.amount)
@@ -258,7 +261,6 @@ class PollNoteViewModel : ViewModel() {
}
}
?: BigDecimal.ZERO
}
fun createZapOptionsThatMatchThePollingParameters(): List<Long> {
val options =
@@ -23,15 +23,11 @@ package com.vitorpamplona.amethyst.ui.note
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.toHexKey
fun ByteArray.toShortenHex(): String {
return toHexKey().toShortenHex()
}
fun ByteArray.toShortenHex(): String = toHexKey().toShortenHex()
fun String.toShortenHex(): String {
if (length <= 16) return this
return replaceRange(8, length - 8, ":")
}
fun HexKey.toDisplayHexKey(): String {
return this.toShortenHex()
}
fun HexKey.toDisplayHexKey(): String = this.toShortenHex()
@@ -161,32 +161,31 @@ fun DrawScope.drawAllQrCodeDataBits(
second = bytes.height,
),
),
)
.forEach { section ->
for (y in section.first.second until section.second.second) {
for (x in section.first.first until section.second.first) {
if (bytes[x, y] == 1.toByte()) {
drawPath(
color = color,
path =
newPath {
addRect(
rect =
Rect(
offset =
Offset(
x = QR_MARGIN_PX + x * size.width,
y = QR_MARGIN_PX + y * size.height,
),
size = size,
),
)
},
)
}
).forEach { section ->
for (y in section.first.second until section.second.second) {
for (x in section.first.first until section.second.first) {
if (bytes[x, y] == 1.toByte()) {
drawPath(
color = color,
path =
newPath {
addRect(
rect =
Rect(
offset =
Offset(
x = QR_MARGIN_PX + x * size.width,
y = QR_MARGIN_PX + y * size.height,
),
size = size,
),
)
},
)
}
}
}
}
}
const val FINDER_PATTERN_ROW_COUNT = 7
@@ -216,15 +215,14 @@ internal fun DrawScope.drawQrCodeFinders(
Offset(x = sideLength - (QR_MARGIN_PX + finderPatternSize.width), y = QR_MARGIN_PX),
// Draw bottom finder pattern.
Offset(x = QR_MARGIN_PX, y = sideLength - (QR_MARGIN_PX + finderPatternSize.height)),
)
.forEach { offset ->
drawQrCodeFinder(
topLeft = offset,
finderPatternSize = finderPatternSize,
cornerRadius = CornerRadius.Zero,
color = color,
)
}
).forEach { offset ->
drawQrCodeFinder(
topLeft = offset,
finderPatternSize = finderPatternSize,
cornerRadius = CornerRadius.Zero,
color = color,
)
}
}
/** This func is responsible for drawing a single finder pattern, for a QR code */
@@ -27,11 +27,15 @@ sealed class AccountState {
object LoggedOff : AccountState()
class LoggedInViewOnly(val account: Account) : AccountState() {
class LoggedInViewOnly(
val account: Account,
) : AccountState() {
val currentViewModelStore = AccountCentricViewModelStore(account)
}
class LoggedIn(val account: Account) : AccountState() {
class LoggedIn(
val account: Account,
) : AccountState() {
val currentViewModelStore = AccountCentricViewModelStore(account)
}
}
@@ -32,37 +32,38 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
@Immutable
abstract class Card() {
abstract class Card {
abstract fun createdAt(): Long
abstract fun id(): String
}
@Immutable
class BadgeCard(val note: Note) : Card() {
override fun createdAt(): Long {
return note.createdAt() ?: 0
}
class BadgeCard(
val note: Note,
) : Card() {
override fun createdAt(): Long = note.createdAt() ?: 0
override fun id() = note.idHex
}
@Immutable
class NoteCard(val note: Note) : Card() {
override fun createdAt(): Long {
return note.createdAt() ?: 0
}
class NoteCard(
val note: Note,
) : Card() {
override fun createdAt(): Long = note.createdAt() ?: 0
override fun id() = note.idHex
}
@Immutable
class ZapUserSetCard(val user: User, val zapEvents: ImmutableList<CombinedZap>) : Card() {
class ZapUserSetCard(
val user: User,
val zapEvents: ImmutableList<CombinedZap>,
) : Card() {
val createdAt = zapEvents.maxOf { it.createdAt() ?: 0 }
override fun createdAt(): Long {
return createdAt
}
override fun createdAt(): Long = createdAt
override fun id() = user.pubkeyHex + "U" + createdAt
}
@@ -95,22 +96,19 @@ class MultiSetCard(
?.content()
?.firstFullCharOrEmoji(ImmutableListOfLists(it.event?.tags() ?: emptyArray()))
?: "+"
}
.mapValues { it.value.toImmutableList() }
}.mapValues { it.value.toImmutableList() }
.toImmutableMap()
override fun createdAt(): Long {
return maxCreatedAt
}
override fun createdAt(): Long = maxCreatedAt
override fun id() = note.idHex + "X" + maxCreatedAt + "X" + minCreatedAt
}
@Immutable
class MessageSetCard(val note: Note) : Card() {
override fun createdAt(): Long {
return note.createdAt() ?: 0
}
class MessageSetCard(
val note: Note,
) : Card() {
override fun createdAt(): Long = note.createdAt() ?: 0
override fun id() = note.idHex
}
@@ -120,10 +118,14 @@ sealed class CardFeedState {
@Immutable object Loading : CardFeedState()
@Stable
class Loaded(val feed: MutableState<ImmutableList<Card>>, val showHidden: MutableState<Boolean>) :
CardFeedState()
class Loaded(
val feed: MutableState<ImmutableList<Card>>,
val showHidden: MutableState<Boolean>,
) : CardFeedState()
@Immutable object Empty : CardFeedState()
@Immutable class FeedError(val errorMessage: String) : CardFeedState()
@Immutable class FeedError(
val errorMessage: String,
) : CardFeedState()
}
@@ -60,17 +60,21 @@ import java.time.format.DateTimeFormatter
import kotlin.time.measureTimedValue
@Stable
class NotificationViewModel(val account: Account) :
CardFeedViewModel(NotificationFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NotificationViewModel : ViewModel> create(modelClass: Class<NotificationViewModel>): NotificationViewModel {
return NotificationViewModel(account) as NotificationViewModel
}
class NotificationViewModel(
val account: Account,
) : CardFeedViewModel(NotificationFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NotificationViewModel : ViewModel> create(modelClass: Class<NotificationViewModel>): NotificationViewModel = NotificationViewModel(account) as NotificationViewModel
}
}
@Stable
open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), InvalidatableViewModel {
open class CardFeedViewModel(
val localFilter: FeedFilter<Note>,
) : ViewModel(),
InvalidatableViewModel {
private val _feedContent = MutableStateFlow<CardFeedState>(CardFeedState.Loading)
val feedContent = _feedContent.asStateFlow()
@@ -167,7 +171,11 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), I
.forEach { zapEvent ->
val zappedPost = zapEvent.replyTo?.lastOrNull()
if (zappedPost != null) {
val zapRequest = zappedPost.zaps.filter { it.value == zapEvent }.keys.firstOrNull()
val zapRequest =
zappedPost.zaps
.filter { it.value == zapEvent }
.keys
.firstOrNull()
if (zapRequest != null) {
// var newZapRequestEvent = LocalCache.checkPrivateZap(zapRequest.event as Event)
// zapRequest.event = newZapRequestEvent
@@ -182,7 +190,11 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), I
LocalCache.getUserIfExists(it) // don't create user if it doesn't exist
}
if (author != null) {
val zapRequest = author.zaps.filter { it.value == zapEvent }.keys.firstOrNull()
val zapRequest =
author.zaps
.filter { it.value == zapEvent }
.keys
.firstOrNull()
if (zapRequest != null) {
zapsPerUser
.getOrPut(author, { mutableListOf() })
@@ -218,7 +230,8 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), I
val singleList =
(boostsInCard + zapsInCard.map { it.response } + reactionsInCard).groupBy {
sdf.format(
Instant.ofEpochSecond(it.createdAt() ?: 0)
Instant
.ofEpochSecond(it.createdAt() ?: 0)
.atZone(ZoneId.systemDefault())
.toLocalDateTime(),
)
@@ -242,10 +255,8 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), I
zapsInCard.filter { it.response in chunk }.toImmutableList(),
)
}
}
.flatten()
}
.flatten()
}.flatten()
}.flatten()
val userZaps =
zapsPerUser
@@ -253,7 +264,8 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), I
val byDay =
user.value.groupBy {
sdf.format(
Instant.ofEpochSecond(it.createdAt() ?: 0)
Instant
.ofEpochSecond(it.createdAt() ?: 0)
.atZone(ZoneId.systemDefault())
.toLocalDateTime(),
)
@@ -268,8 +280,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), I
.toImmutableList(),
)
}
}
.flatten()
}.flatten()
val textNoteCards =
notes
@@ -278,8 +289,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), I
it.event !is RepostEvent &&
it.event !is GenericRepostEvent &&
it.event !is LnZapEvent
}
.map {
}.map {
if (it.event is PrivateDmEvent || it.event is ChatMessageEvent) {
MessageSetCard(it)
} else if (it.event is BadgeAwardEvent) {
@@ -464,7 +474,10 @@ fun <T> equalImmutableLists(
}
@Immutable
data class CombinedZap(val request: Note, val response: Note) {
data class CombinedZap(
val request: Note,
val response: Note,
) {
fun createdAt() = response.createdAt()
fun idHex() = response.idHex
@@ -34,5 +34,7 @@ sealed class FeedState {
object Empty : FeedState()
class FeedError(val errorMessage: String) : FeedState()
class FeedError(
val errorMessage: String,
) : FeedState()
}
@@ -73,248 +73,302 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class NostrChannelFeedViewModel(val channel: Channel, val account: Account) :
FeedViewModel(ChannelFeedFilter(channel, account)) {
class Factory(val channel: Channel, val account: Account) : ViewModelProvider.Factory {
override fun <NostrChannelFeedViewModel : ViewModel> create(modelClass: Class<NostrChannelFeedViewModel>): NostrChannelFeedViewModel {
return NostrChannelFeedViewModel(channel, account) as NostrChannelFeedViewModel
}
class NostrChannelFeedViewModel(
val channel: Channel,
val account: Account,
) : FeedViewModel(ChannelFeedFilter(channel, account)) {
class Factory(
val channel: Channel,
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrChannelFeedViewModel : ViewModel> create(modelClass: Class<NostrChannelFeedViewModel>): NostrChannelFeedViewModel = NostrChannelFeedViewModel(channel, account) as NostrChannelFeedViewModel
}
}
class NostrChatroomFeedViewModel(val user: ChatroomKey, val account: Account) :
FeedViewModel(ChatroomFeedFilter(user, account)) {
class Factory(val user: ChatroomKey, val account: Account) : ViewModelProvider.Factory {
override fun <NostrChatRoomFeedViewModel : ViewModel> create(modelClass: Class<NostrChatRoomFeedViewModel>): NostrChatRoomFeedViewModel {
return NostrChatroomFeedViewModel(user, account) as NostrChatRoomFeedViewModel
}
class NostrChatroomFeedViewModel(
val user: ChatroomKey,
val account: Account,
) : FeedViewModel(ChatroomFeedFilter(user, account)) {
class Factory(
val user: ChatroomKey,
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrChatRoomFeedViewModel : ViewModel> create(modelClass: Class<NostrChatRoomFeedViewModel>): NostrChatRoomFeedViewModel = NostrChatroomFeedViewModel(user, account) as NostrChatRoomFeedViewModel
}
}
@Stable
class NostrVideoFeedViewModel(val account: Account) : FeedViewModel(VideoFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrVideoFeedViewModel : ViewModel> create(modelClass: Class<NostrVideoFeedViewModel>): NostrVideoFeedViewModel {
return NostrVideoFeedViewModel(account) as NostrVideoFeedViewModel
}
class NostrVideoFeedViewModel(
val account: Account,
) : FeedViewModel(VideoFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrVideoFeedViewModel : ViewModel> create(modelClass: Class<NostrVideoFeedViewModel>): NostrVideoFeedViewModel = NostrVideoFeedViewModel(account) as NostrVideoFeedViewModel
}
}
class NostrDiscoverMarketplaceFeedViewModel(val account: Account) :
FeedViewModel(
class NostrDiscoverMarketplaceFeedViewModel(
val account: Account,
) : FeedViewModel(
DiscoverMarketplaceFeedFilter(account),
) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrDiscoverMarketplaceFeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverMarketplaceFeedViewModel>): NostrDiscoverMarketplaceFeedViewModel {
return NostrDiscoverMarketplaceFeedViewModel(account) as NostrDiscoverMarketplaceFeedViewModel
}
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrDiscoverMarketplaceFeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverMarketplaceFeedViewModel>): NostrDiscoverMarketplaceFeedViewModel = NostrDiscoverMarketplaceFeedViewModel(account) as NostrDiscoverMarketplaceFeedViewModel
}
}
class NostrDiscoverNIP89FeedViewModel(val account: Account) :
FeedViewModel(
class NostrDiscoverNIP89FeedViewModel(
val account: Account,
) : FeedViewModel(
DiscoverNIP89FeedFilter(account),
) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrDiscoverNIP89FeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverNIP89FeedViewModel>): NostrDiscoverNIP89FeedViewModel {
return NostrDiscoverNIP89FeedViewModel(account) as NostrDiscoverNIP89FeedViewModel
}
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrDiscoverNIP89FeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverNIP89FeedViewModel>): NostrDiscoverNIP89FeedViewModel = NostrDiscoverNIP89FeedViewModel(account) as NostrDiscoverNIP89FeedViewModel
}
}
class NostrDiscoverLiveFeedViewModel(val account: Account) :
FeedViewModel(DiscoverLiveFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrDiscoverLiveFeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverLiveFeedViewModel>): NostrDiscoverLiveFeedViewModel {
return NostrDiscoverLiveFeedViewModel(account) as NostrDiscoverLiveFeedViewModel
}
class NostrDiscoverLiveFeedViewModel(
val account: Account,
) : FeedViewModel(DiscoverLiveFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrDiscoverLiveFeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverLiveFeedViewModel>): NostrDiscoverLiveFeedViewModel = NostrDiscoverLiveFeedViewModel(account) as NostrDiscoverLiveFeedViewModel
}
}
class NostrDiscoverCommunityFeedViewModel(val account: Account) :
FeedViewModel(DiscoverCommunityFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrDiscoverCommunityFeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverCommunityFeedViewModel>): NostrDiscoverCommunityFeedViewModel {
return NostrDiscoverCommunityFeedViewModel(account) as NostrDiscoverCommunityFeedViewModel
}
class NostrDiscoverCommunityFeedViewModel(
val account: Account,
) : FeedViewModel(DiscoverCommunityFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrDiscoverCommunityFeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverCommunityFeedViewModel>): NostrDiscoverCommunityFeedViewModel = NostrDiscoverCommunityFeedViewModel(account) as NostrDiscoverCommunityFeedViewModel
}
}
class NostrDiscoverChatFeedViewModel(val account: Account) :
FeedViewModel(DiscoverChatFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrDiscoverChatFeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverChatFeedViewModel>): NostrDiscoverChatFeedViewModel {
return NostrDiscoverChatFeedViewModel(account) as NostrDiscoverChatFeedViewModel
}
class NostrDiscoverChatFeedViewModel(
val account: Account,
) : FeedViewModel(DiscoverChatFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrDiscoverChatFeedViewModel : ViewModel> create(modelClass: Class<NostrDiscoverChatFeedViewModel>): NostrDiscoverChatFeedViewModel = NostrDiscoverChatFeedViewModel(account) as NostrDiscoverChatFeedViewModel
}
}
class NostrThreadFeedViewModel(account: Account, noteId: String) :
FeedViewModel(ThreadFeedFilter(account, noteId)) {
class Factory(val account: Account, val noteId: String) : ViewModelProvider.Factory {
override fun <NostrThreadFeedViewModel : ViewModel> create(modelClass: Class<NostrThreadFeedViewModel>): NostrThreadFeedViewModel {
return NostrThreadFeedViewModel(account, noteId) as NostrThreadFeedViewModel
}
class NostrThreadFeedViewModel(
account: Account,
noteId: String,
) : FeedViewModel(ThreadFeedFilter(account, noteId)) {
class Factory(
val account: Account,
val noteId: String,
) : ViewModelProvider.Factory {
override fun <NostrThreadFeedViewModel : ViewModel> create(modelClass: Class<NostrThreadFeedViewModel>): NostrThreadFeedViewModel = NostrThreadFeedViewModel(account, noteId) as NostrThreadFeedViewModel
}
}
class NostrUserProfileNewThreadsFeedViewModel(val user: User, val account: Account) :
FeedViewModel(UserProfileNewThreadFeedFilter(user, account)) {
class Factory(val user: User, val account: Account) : ViewModelProvider.Factory {
override fun <NostrUserProfileNewThreadsFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileNewThreadsFeedViewModel>): NostrUserProfileNewThreadsFeedViewModel {
return NostrUserProfileNewThreadsFeedViewModel(user, account)
class NostrUserProfileNewThreadsFeedViewModel(
val user: User,
val account: Account,
) : FeedViewModel(UserProfileNewThreadFeedFilter(user, account)) {
class Factory(
val user: User,
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrUserProfileNewThreadsFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileNewThreadsFeedViewModel>): NostrUserProfileNewThreadsFeedViewModel =
NostrUserProfileNewThreadsFeedViewModel(user, account)
as NostrUserProfileNewThreadsFeedViewModel
}
}
}
class NostrUserProfileConversationsFeedViewModel(val user: User, val account: Account) :
FeedViewModel(UserProfileConversationsFeedFilter(user, account)) {
class Factory(val user: User, val account: Account) : ViewModelProvider.Factory {
override fun <NostrUserProfileConversationsFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileConversationsFeedViewModel>): NostrUserProfileConversationsFeedViewModel {
return NostrUserProfileConversationsFeedViewModel(user, account)
class NostrUserProfileConversationsFeedViewModel(
val user: User,
val account: Account,
) : FeedViewModel(UserProfileConversationsFeedFilter(user, account)) {
class Factory(
val user: User,
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrUserProfileConversationsFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileConversationsFeedViewModel>): NostrUserProfileConversationsFeedViewModel =
NostrUserProfileConversationsFeedViewModel(user, account)
as NostrUserProfileConversationsFeedViewModel
}
}
}
class NostrHashtagFeedViewModel(val hashtag: String, val account: Account) :
FeedViewModel(HashtagFeedFilter(hashtag, account)) {
class Factory(val hashtag: String, val account: Account) : ViewModelProvider.Factory {
override fun <NostrHashtagFeedViewModel : ViewModel> create(modelClass: Class<NostrHashtagFeedViewModel>): NostrHashtagFeedViewModel {
return NostrHashtagFeedViewModel(hashtag, account) as NostrHashtagFeedViewModel
}
class NostrHashtagFeedViewModel(
val hashtag: String,
val account: Account,
) : FeedViewModel(HashtagFeedFilter(hashtag, account)) {
class Factory(
val hashtag: String,
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrHashtagFeedViewModel : ViewModel> create(modelClass: Class<NostrHashtagFeedViewModel>): NostrHashtagFeedViewModel = NostrHashtagFeedViewModel(hashtag, account) as NostrHashtagFeedViewModel
}
}
class NostrGeoHashFeedViewModel(val geohash: String, val account: Account) :
FeedViewModel(GeoHashFeedFilter(geohash, account)) {
class Factory(val geohash: String, val account: Account) : ViewModelProvider.Factory {
override fun <NostrGeoHashFeedViewModel : ViewModel> create(modelClass: Class<NostrGeoHashFeedViewModel>): NostrGeoHashFeedViewModel {
return NostrGeoHashFeedViewModel(geohash, account) as NostrGeoHashFeedViewModel
}
class NostrGeoHashFeedViewModel(
val geohash: String,
val account: Account,
) : FeedViewModel(GeoHashFeedFilter(geohash, account)) {
class Factory(
val geohash: String,
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrGeoHashFeedViewModel : ViewModel> create(modelClass: Class<NostrGeoHashFeedViewModel>): NostrGeoHashFeedViewModel = NostrGeoHashFeedViewModel(geohash, account) as NostrGeoHashFeedViewModel
}
}
class NostrCommunityFeedViewModel(val note: AddressableNote, val account: Account) :
FeedViewModel(CommunityFeedFilter(note, account)) {
class Factory(val note: AddressableNote, val account: Account) : ViewModelProvider.Factory {
override fun <NostrCommunityFeedViewModel : ViewModel> create(modelClass: Class<NostrCommunityFeedViewModel>): NostrCommunityFeedViewModel {
return NostrCommunityFeedViewModel(note, account) as NostrCommunityFeedViewModel
}
class NostrCommunityFeedViewModel(
val note: AddressableNote,
val account: Account,
) : FeedViewModel(CommunityFeedFilter(note, account)) {
class Factory(
val note: AddressableNote,
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrCommunityFeedViewModel : ViewModel> create(modelClass: Class<NostrCommunityFeedViewModel>): NostrCommunityFeedViewModel = NostrCommunityFeedViewModel(note, account) as NostrCommunityFeedViewModel
}
}
class NostrUserProfileReportFeedViewModel(val user: User) :
FeedViewModel(UserProfileReportsFeedFilter(user)) {
class Factory(val user: User) : ViewModelProvider.Factory {
override fun <NostrUserProfileReportFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileReportFeedViewModel>): NostrUserProfileReportFeedViewModel {
return NostrUserProfileReportFeedViewModel(user) as NostrUserProfileReportFeedViewModel
}
class NostrUserProfileReportFeedViewModel(
val user: User,
) : FeedViewModel(UserProfileReportsFeedFilter(user)) {
class Factory(
val user: User,
) : ViewModelProvider.Factory {
override fun <NostrUserProfileReportFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileReportFeedViewModel>): NostrUserProfileReportFeedViewModel = NostrUserProfileReportFeedViewModel(user) as NostrUserProfileReportFeedViewModel
}
}
class NostrUserProfileBookmarksFeedViewModel(val user: User, val account: Account) :
FeedViewModel(UserProfileBookmarksFeedFilter(user, account)) {
class Factory(val user: User, val account: Account) : ViewModelProvider.Factory {
override fun <NostrUserProfileBookmarksFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileBookmarksFeedViewModel>): NostrUserProfileBookmarksFeedViewModel {
return NostrUserProfileBookmarksFeedViewModel(user, account)
class NostrUserProfileBookmarksFeedViewModel(
val user: User,
val account: Account,
) : FeedViewModel(UserProfileBookmarksFeedFilter(user, account)) {
class Factory(
val user: User,
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrUserProfileBookmarksFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileBookmarksFeedViewModel>): NostrUserProfileBookmarksFeedViewModel =
NostrUserProfileBookmarksFeedViewModel(user, account)
as NostrUserProfileBookmarksFeedViewModel
}
}
}
class NostrChatroomListKnownFeedViewModel(val account: Account) :
FeedViewModel(ChatroomListKnownFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrChatroomListKnownFeedViewModel : ViewModel> create(modelClass: Class<NostrChatroomListKnownFeedViewModel>): NostrChatroomListKnownFeedViewModel {
return NostrChatroomListKnownFeedViewModel(account) as NostrChatroomListKnownFeedViewModel
}
class NostrChatroomListKnownFeedViewModel(
val account: Account,
) : FeedViewModel(ChatroomListKnownFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrChatroomListKnownFeedViewModel : ViewModel> create(modelClass: Class<NostrChatroomListKnownFeedViewModel>): NostrChatroomListKnownFeedViewModel = NostrChatroomListKnownFeedViewModel(account) as NostrChatroomListKnownFeedViewModel
}
}
class NostrChatroomListNewFeedViewModel(val account: Account) :
FeedViewModel(ChatroomListNewFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrChatroomListNewFeedViewModel : ViewModel> create(modelClass: Class<NostrChatroomListNewFeedViewModel>): NostrChatroomListNewFeedViewModel {
return NostrChatroomListNewFeedViewModel(account) as NostrChatroomListNewFeedViewModel
}
class NostrChatroomListNewFeedViewModel(
val account: Account,
) : FeedViewModel(ChatroomListNewFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrChatroomListNewFeedViewModel : ViewModel> create(modelClass: Class<NostrChatroomListNewFeedViewModel>): NostrChatroomListNewFeedViewModel = NostrChatroomListNewFeedViewModel(account) as NostrChatroomListNewFeedViewModel
}
}
@Stable
class NostrHomeFeedViewModel(val account: Account) :
FeedViewModel(HomeNewThreadFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrHomeFeedViewModel : ViewModel> create(modelClass: Class<NostrHomeFeedViewModel>): NostrHomeFeedViewModel {
return NostrHomeFeedViewModel(account) as NostrHomeFeedViewModel
}
class NostrHomeFeedViewModel(
val account: Account,
) : FeedViewModel(HomeNewThreadFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrHomeFeedViewModel : ViewModel> create(modelClass: Class<NostrHomeFeedViewModel>): NostrHomeFeedViewModel = NostrHomeFeedViewModel(account) as NostrHomeFeedViewModel
}
}
@Stable
class NostrHomeRepliesFeedViewModel(val account: Account) :
FeedViewModel(HomeConversationsFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrHomeRepliesFeedViewModel : ViewModel> create(modelClass: Class<NostrHomeRepliesFeedViewModel>): NostrHomeRepliesFeedViewModel {
return NostrHomeRepliesFeedViewModel(account) as NostrHomeRepliesFeedViewModel
}
class NostrHomeRepliesFeedViewModel(
val account: Account,
) : FeedViewModel(HomeConversationsFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrHomeRepliesFeedViewModel : ViewModel> create(modelClass: Class<NostrHomeRepliesFeedViewModel>): NostrHomeRepliesFeedViewModel = NostrHomeRepliesFeedViewModel(account) as NostrHomeRepliesFeedViewModel
}
}
@Stable
class NostrBookmarkPublicFeedViewModel(val account: Account) :
FeedViewModel(BookmarkPublicFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrBookmarkPublicFeedViewModel : ViewModel> create(modelClass: Class<NostrBookmarkPublicFeedViewModel>): NostrBookmarkPublicFeedViewModel {
return NostrBookmarkPublicFeedViewModel(account) as NostrBookmarkPublicFeedViewModel
}
class NostrBookmarkPublicFeedViewModel(
val account: Account,
) : FeedViewModel(BookmarkPublicFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrBookmarkPublicFeedViewModel : ViewModel> create(modelClass: Class<NostrBookmarkPublicFeedViewModel>): NostrBookmarkPublicFeedViewModel = NostrBookmarkPublicFeedViewModel(account) as NostrBookmarkPublicFeedViewModel
}
}
@Stable
class NostrBookmarkPrivateFeedViewModel(val account: Account) :
FeedViewModel(BookmarkPrivateFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrBookmarkPrivateFeedViewModel : ViewModel> create(modelClass: Class<NostrBookmarkPrivateFeedViewModel>): NostrBookmarkPrivateFeedViewModel {
return NostrBookmarkPrivateFeedViewModel(account) as NostrBookmarkPrivateFeedViewModel
}
class NostrBookmarkPrivateFeedViewModel(
val account: Account,
) : FeedViewModel(BookmarkPrivateFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrBookmarkPrivateFeedViewModel : ViewModel> create(modelClass: Class<NostrBookmarkPrivateFeedViewModel>): NostrBookmarkPrivateFeedViewModel = NostrBookmarkPrivateFeedViewModel(account) as NostrBookmarkPrivateFeedViewModel
}
}
@Stable
class NostrNIP90ContentDiscoveryFeedViewModel(val account: Account, val dvmkey: String, val requestid: String) :
FeedViewModel(NIP90ContentDiscoveryResponseFilter(account, dvmkey, requestid)) {
class Factory(val account: Account, val dvmkey: String, val requestid: String) : ViewModelProvider.Factory {
override fun <NostrNIP90ContentDiscoveryFeedViewModel : ViewModel> create(modelClass: Class<NostrNIP90ContentDiscoveryFeedViewModel>): NostrNIP90ContentDiscoveryFeedViewModel {
return NostrNIP90ContentDiscoveryFeedViewModel(account, dvmkey, requestid) as NostrNIP90ContentDiscoveryFeedViewModel
}
class NostrNIP90ContentDiscoveryFeedViewModel(
val account: Account,
val dvmkey: String,
val requestid: String,
) : FeedViewModel(NIP90ContentDiscoveryResponseFilter(account, dvmkey, requestid)) {
class Factory(
val account: Account,
val dvmkey: String,
val requestid: String,
) : ViewModelProvider.Factory {
override fun <NostrNIP90ContentDiscoveryFeedViewModel : ViewModel> create(modelClass: Class<NostrNIP90ContentDiscoveryFeedViewModel>): NostrNIP90ContentDiscoveryFeedViewModel = NostrNIP90ContentDiscoveryFeedViewModel(account, dvmkey, requestid) as NostrNIP90ContentDiscoveryFeedViewModel
}
}
@Stable
class NostrDraftEventsFeedViewModel(val account: Account) :
FeedViewModel(DraftEventsFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrDraftEventsFeedViewModel : ViewModel> create(modelClass: Class<NostrDraftEventsFeedViewModel>): NostrDraftEventsFeedViewModel {
return NostrDraftEventsFeedViewModel(account) as NostrDraftEventsFeedViewModel
}
class NostrDraftEventsFeedViewModel(
val account: Account,
) : FeedViewModel(DraftEventsFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrDraftEventsFeedViewModel : ViewModel> create(modelClass: Class<NostrDraftEventsFeedViewModel>): NostrDraftEventsFeedViewModel = NostrDraftEventsFeedViewModel(account) as NostrDraftEventsFeedViewModel
}
}
class NostrUserAppRecommendationsFeedViewModel(val user: User) :
FeedViewModel(UserProfileAppRecommendationsFeedFilter(user)) {
class Factory(val user: User) : ViewModelProvider.Factory {
override fun <NostrUserAppRecommendationsFeedViewModel : ViewModel> create(modelClass: Class<NostrUserAppRecommendationsFeedViewModel>): NostrUserAppRecommendationsFeedViewModel {
return NostrUserAppRecommendationsFeedViewModel(user)
class NostrUserAppRecommendationsFeedViewModel(
val user: User,
) : FeedViewModel(UserProfileAppRecommendationsFeedFilter(user)) {
class Factory(
val user: User,
) : ViewModelProvider.Factory {
override fun <NostrUserAppRecommendationsFeedViewModel : ViewModel> create(modelClass: Class<NostrUserAppRecommendationsFeedViewModel>): NostrUserAppRecommendationsFeedViewModel =
NostrUserAppRecommendationsFeedViewModel(user)
as NostrUserAppRecommendationsFeedViewModel
}
}
}
@Stable
abstract class FeedViewModel(val localFilter: FeedFilter<Note>) :
ViewModel(), InvalidatableViewModel {
abstract class FeedViewModel(
val localFilter: FeedFilter<Note>,
) : ViewModel(),
InvalidatableViewModel {
private val _feedContent = MutableStateFlow<FeedState>(FeedState.Loading)
val feedContent = _feedContent.asStateFlow()
@@ -391,7 +445,9 @@ abstract class FeedViewModel(val localFilter: FeedFilter<Note>) :
} else {
val deletedEventIds = deletionEvents.flatMapTo(HashSet()) { it.deleteEvents() }
val deletedEventAddresses = deletionEvents.flatMapTo(HashSet()) { it.deleteAddresses() }
oldNotesState.feed.value.filter { !it.wasOrShouldBeDeletedBy(deletedEventIds, deletedEventAddresses) }.toImmutableList()
oldNotesState.feed.value
.filter { !it.wasOrShouldBeDeletedBy(deletedEventIds, deletedEventAddresses) }
.toImmutableList()
}
val newList =
@@ -26,15 +26,22 @@ import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.model.Note
import kotlinx.collections.immutable.ImmutableList
@Immutable data class ZapReqResponse(val zapRequest: Note, val zapEvent: Note)
@Immutable data class ZapReqResponse(
val zapRequest: Note,
val zapEvent: Note,
)
@Stable
sealed class LnZapFeedState {
object Loading : LnZapFeedState()
class Loaded(val feed: MutableState<ImmutableList<ZapReqResponse>>) : LnZapFeedState()
class Loaded(
val feed: MutableState<ImmutableList<ZapReqResponse>>,
) : LnZapFeedState()
object Empty : LnZapFeedState()
class FeedError(val errorMessage: String) : LnZapFeedState()
class FeedError(
val errorMessage: String,
) : LnZapFeedState()
}
@@ -41,17 +41,20 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class NostrUserProfileZapsFeedViewModel(user: User) :
LnZapFeedViewModel(UserProfileZapsFeedFilter(user)) {
class Factory(val user: User) : ViewModelProvider.Factory {
override fun <NostrUserProfileZapsFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileZapsFeedViewModel>): NostrUserProfileZapsFeedViewModel {
return NostrUserProfileZapsFeedViewModel(user) as NostrUserProfileZapsFeedViewModel
}
class NostrUserProfileZapsFeedViewModel(
user: User,
) : LnZapFeedViewModel(UserProfileZapsFeedFilter(user)) {
class Factory(
val user: User,
) : ViewModelProvider.Factory {
override fun <NostrUserProfileZapsFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileZapsFeedViewModel>): NostrUserProfileZapsFeedViewModel = NostrUserProfileZapsFeedViewModel(user) as NostrUserProfileZapsFeedViewModel
}
}
@Stable
open class LnZapFeedViewModel(val dataSource: FeedFilter<ZapReqResponse>) : ViewModel() {
open class LnZapFeedViewModel(
val dataSource: FeedFilter<ZapReqResponse>,
) : ViewModel() {
private val _feedContent = MutableStateFlow<LnZapFeedState>(LnZapFeedState.Loading)
val feedContent = _feedContent.asStateFlow()
@@ -50,7 +50,9 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@Stable
class RelayFeedViewModel : ViewModel(), InvalidatableViewModel {
class RelayFeedViewModel :
ViewModel(),
InvalidatableViewModel {
val order =
compareByDescending<RelayInfo> { it.lastEvent }
.thenByDescending { it.counter }
@@ -26,9 +26,13 @@ import kotlinx.collections.immutable.ImmutableList
sealed class StringFeedState {
object Loading : StringFeedState()
class Loaded(val feed: MutableState<ImmutableList<String>>) : StringFeedState()
class Loaded(
val feed: MutableState<ImmutableList<String>>,
) : StringFeedState()
object Empty : StringFeedState()
class FeedError(val errorMessage: String) : StringFeedState()
class FeedError(
val errorMessage: String,
) : StringFeedState()
}
@@ -41,20 +41,23 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class NostrHiddenWordsFeedViewModel(val account: Account) :
StringFeedViewModel(
class NostrHiddenWordsFeedViewModel(
val account: Account,
) : StringFeedViewModel(
HiddenWordsFeedFilter(account),
) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrHiddenWordsFeedViewModel : ViewModel> create(modelClass: Class<NostrHiddenWordsFeedViewModel>): NostrHiddenWordsFeedViewModel {
return NostrHiddenWordsFeedViewModel(account) as NostrHiddenWordsFeedViewModel
}
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrHiddenWordsFeedViewModel : ViewModel> create(modelClass: Class<NostrHiddenWordsFeedViewModel>): NostrHiddenWordsFeedViewModel = NostrHiddenWordsFeedViewModel(account) as NostrHiddenWordsFeedViewModel
}
}
@Stable
open class StringFeedViewModel(val dataSource: FeedFilter<String>) :
ViewModel(), InvalidatableViewModel {
open class StringFeedViewModel(
val dataSource: FeedFilter<String>,
) : ViewModel(),
InvalidatableViewModel {
private val _feedContent = MutableStateFlow<StringFeedState>(StringFeedState.Loading)
val feedContent = _feedContent.asStateFlow()
@@ -27,9 +27,13 @@ import kotlinx.collections.immutable.ImmutableList
sealed class UserFeedState {
object Loading : UserFeedState()
class Loaded(val feed: MutableState<ImmutableList<User>>) : UserFeedState()
class Loaded(
val feed: MutableState<ImmutableList<User>>,
) : UserFeedState()
object Empty : UserFeedState()
class FeedError(val errorMessage: String) : UserFeedState()
class FeedError(
val errorMessage: String,
) : UserFeedState()
}
@@ -45,47 +45,59 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
class NostrUserProfileFollowsUserFeedViewModel(val user: User, val account: Account) :
UserFeedViewModel(UserProfileFollowsFeedFilter(user, account)) {
class Factory(val user: User, val account: Account) : ViewModelProvider.Factory {
override fun <NostrUserProfileFollowsUserFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileFollowsUserFeedViewModel>): NostrUserProfileFollowsUserFeedViewModel {
return NostrUserProfileFollowsUserFeedViewModel(user, account)
class NostrUserProfileFollowsUserFeedViewModel(
val user: User,
val account: Account,
) : UserFeedViewModel(UserProfileFollowsFeedFilter(user, account)) {
class Factory(
val user: User,
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrUserProfileFollowsUserFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileFollowsUserFeedViewModel>): NostrUserProfileFollowsUserFeedViewModel =
NostrUserProfileFollowsUserFeedViewModel(user, account)
as NostrUserProfileFollowsUserFeedViewModel
}
}
}
class NostrUserProfileFollowersUserFeedViewModel(val user: User, val account: Account) :
UserFeedViewModel(UserProfileFollowersFeedFilter(user, account)) {
class Factory(val user: User, val account: Account) : ViewModelProvider.Factory {
override fun <NostrUserProfileFollowersUserFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileFollowersUserFeedViewModel>): NostrUserProfileFollowersUserFeedViewModel {
return NostrUserProfileFollowersUserFeedViewModel(user, account)
class NostrUserProfileFollowersUserFeedViewModel(
val user: User,
val account: Account,
) : UserFeedViewModel(UserProfileFollowersFeedFilter(user, account)) {
class Factory(
val user: User,
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrUserProfileFollowersUserFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileFollowersUserFeedViewModel>): NostrUserProfileFollowersUserFeedViewModel =
NostrUserProfileFollowersUserFeedViewModel(user, account)
as NostrUserProfileFollowersUserFeedViewModel
}
}
}
class NostrHiddenAccountsFeedViewModel(val account: Account) :
UserFeedViewModel(HiddenAccountsFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrHiddenAccountsFeedViewModel : ViewModel> create(modelClass: Class<NostrHiddenAccountsFeedViewModel>): NostrHiddenAccountsFeedViewModel {
return NostrHiddenAccountsFeedViewModel(account) as NostrHiddenAccountsFeedViewModel
}
class NostrHiddenAccountsFeedViewModel(
val account: Account,
) : UserFeedViewModel(HiddenAccountsFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrHiddenAccountsFeedViewModel : ViewModel> create(modelClass: Class<NostrHiddenAccountsFeedViewModel>): NostrHiddenAccountsFeedViewModel = NostrHiddenAccountsFeedViewModel(account) as NostrHiddenAccountsFeedViewModel
}
}
class NostrSpammerAccountsFeedViewModel(val account: Account) :
UserFeedViewModel(SpammerAccountsFeedFilter(account)) {
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <NostrSpammerAccountsFeedViewModel : ViewModel> create(modelClass: Class<NostrSpammerAccountsFeedViewModel>): NostrSpammerAccountsFeedViewModel {
return NostrSpammerAccountsFeedViewModel(account) as NostrSpammerAccountsFeedViewModel
}
class NostrSpammerAccountsFeedViewModel(
val account: Account,
) : UserFeedViewModel(SpammerAccountsFeedFilter(account)) {
class Factory(
val account: Account,
) : ViewModelProvider.Factory {
override fun <NostrSpammerAccountsFeedViewModel : ViewModel> create(modelClass: Class<NostrSpammerAccountsFeedViewModel>): NostrSpammerAccountsFeedViewModel = NostrSpammerAccountsFeedViewModel(account) as NostrSpammerAccountsFeedViewModel
}
}
@Stable
open class UserFeedViewModel(val dataSource: FeedFilter<User>) :
ViewModel(), InvalidatableViewModel {
open class UserFeedViewModel(
val dataSource: FeedFilter<User>,
) : ViewModel(),
InvalidatableViewModel {
private val _feedContent = MutableStateFlow<UserFeedState>(UserFeedState.Loading)
val feedContent = _feedContent.asStateFlow()

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