Finish the migration from LiveData to Flow
Moves most of the flow updates away from the Main thread. Removes the need to use the Main thread to access Account.userProfile
This commit is contained in:
@@ -201,9 +201,6 @@ dependencies {
|
|||||||
// Navigation
|
// Navigation
|
||||||
implementation libs.androidx.navigation.compose
|
implementation libs.androidx.navigation.compose
|
||||||
|
|
||||||
// Observe Live data as State
|
|
||||||
implementation libs.androidx.runtime.livedata
|
|
||||||
|
|
||||||
// Material 3 Design
|
// Material 3 Design
|
||||||
implementation libs.androidx.material3
|
implementation libs.androidx.material3
|
||||||
implementation libs.androidx.material.icons
|
implementation libs.androidx.material.icons
|
||||||
@@ -216,7 +213,6 @@ dependencies {
|
|||||||
implementation libs.androidx.lifecycle.runtime.ktx
|
implementation libs.androidx.lifecycle.runtime.ktx
|
||||||
implementation libs.androidx.lifecycle.runtime.compose
|
implementation libs.androidx.lifecycle.runtime.compose
|
||||||
implementation libs.androidx.lifecycle.viewmodel.compose
|
implementation libs.androidx.lifecycle.viewmodel.compose
|
||||||
implementation libs.androidx.lifecycle.livedata.ktx
|
|
||||||
|
|
||||||
// Zoomable images
|
// Zoomable images
|
||||||
implementation libs.zoomable
|
implementation libs.zoomable
|
||||||
|
|||||||
@@ -72,8 +72,6 @@ fun debugState(context: Context) {
|
|||||||
Log.d(
|
Log.d(
|
||||||
"STATE DUMP",
|
"STATE DUMP",
|
||||||
"Notes: " +
|
"Notes: " +
|
||||||
LocalCache.notes.filter { _, it -> it.liveSet != null }.size +
|
|
||||||
" / " +
|
|
||||||
LocalCache.notes.filter { _, it -> it.flowSet != null }.size +
|
LocalCache.notes.filter { _, it -> it.flowSet != null }.size +
|
||||||
" / " +
|
" / " +
|
||||||
LocalCache.notes.filter { _, it -> it.event != null }.size +
|
LocalCache.notes.filter { _, it -> it.event != null }.size +
|
||||||
@@ -83,8 +81,6 @@ fun debugState(context: Context) {
|
|||||||
Log.d(
|
Log.d(
|
||||||
"STATE DUMP",
|
"STATE DUMP",
|
||||||
"Addressables: " +
|
"Addressables: " +
|
||||||
LocalCache.addressables.filter { _, it -> it.liveSet != null }.size +
|
|
||||||
" / " +
|
|
||||||
LocalCache.addressables.filter { _, it -> it.flowSet != null }.size +
|
LocalCache.addressables.filter { _, it -> it.flowSet != null }.size +
|
||||||
" / " +
|
" / " +
|
||||||
LocalCache.addressables.filter { _, it -> it.event != null }.size +
|
LocalCache.addressables.filter { _, it -> it.event != null }.size +
|
||||||
@@ -94,8 +90,6 @@ fun debugState(context: Context) {
|
|||||||
Log.d(
|
Log.d(
|
||||||
"STATE DUMP",
|
"STATE DUMP",
|
||||||
"Users: " +
|
"Users: " +
|
||||||
LocalCache.users.filter { _, it -> it.liveSet != null }.size +
|
|
||||||
" / " +
|
|
||||||
LocalCache.users.filter { _, it -> it.flowSet != null }.size +
|
LocalCache.users.filter { _, it -> it.flowSet != null }.size +
|
||||||
" / " +
|
" / " +
|
||||||
LocalCache.users.filter { _, it -> it.latestMetadata != null }.size +
|
LocalCache.users.filter { _, it -> it.latestMetadata != null }.size +
|
||||||
@@ -169,3 +163,21 @@ inline fun <T> logTime(
|
|||||||
} else {
|
} else {
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun debug(
|
||||||
|
tag: String,
|
||||||
|
debugMessage: String,
|
||||||
|
) {
|
||||||
|
if (isDebug) {
|
||||||
|
Log.d(tag, debugMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun debug(
|
||||||
|
tag: String,
|
||||||
|
debugMessage: () -> String,
|
||||||
|
) {
|
||||||
|
if (isDebug) {
|
||||||
|
Log.d(tag, debugMessage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,10 +23,6 @@ package com.vitorpamplona.amethyst.model
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.asLiveData
|
|
||||||
import androidx.lifecycle.liveData
|
|
||||||
import androidx.lifecycle.switchMap
|
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import com.fonfon.kgeohash.GeoHash
|
import com.fonfon.kgeohash.GeoHash
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
@@ -192,6 +188,7 @@ import kotlinx.coroutines.flow.SharingStarted
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.combineTransform
|
import kotlinx.coroutines.flow.combineTransform
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.emitAll
|
import kotlinx.coroutines.flow.emitAll
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -584,7 +581,6 @@ class Account(
|
|||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
val liveKind3FollowsFlow: Flow<LiveFollowList> =
|
val liveKind3FollowsFlow: Flow<LiveFollowList> =
|
||||||
userProfile().flow().follows.stateFlow.transformLatest {
|
userProfile().flow().follows.stateFlow.transformLatest {
|
||||||
checkNotInMainThread()
|
|
||||||
emit(buildFollowLists(it.user.latestContactList))
|
emit(buildFollowLists(it.user.latestContactList))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1090,24 +1086,25 @@ class Account(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val liveHiddenUsers = flowHiddenUsers.asLiveData()
|
val decryptBookmarks: Flow<BookmarkListEvent?> by lazy {
|
||||||
|
userProfile()
|
||||||
val decryptBookmarks: LiveData<BookmarkListEvent?> by lazy {
|
.flow()
|
||||||
userProfile().live().bookmarks.switchMap { userState ->
|
.bookmarks.stateFlow
|
||||||
liveData(Dispatchers.IO) {
|
.map { userState ->
|
||||||
if (userState.user.latestBookmarkList == null) {
|
if (userState.user.latestBookmarkList == null) {
|
||||||
emit(null)
|
null
|
||||||
} else {
|
} else {
|
||||||
emit(
|
val result =
|
||||||
tryAndWait { continuation ->
|
tryAndWait { continuation ->
|
||||||
userState.user.latestBookmarkList?.privateTags(signer) {
|
userState.user.latestBookmarkList?.privateTags(signer) {
|
||||||
continuation.resume(userState.user.latestBookmarkList)
|
continuation.resume(userState.user.latestBookmarkList)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}.debounce(1000)
|
||||||
}
|
.flowOn(Dispatchers.Default)
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmojiMedia(
|
class EmojiMedia(
|
||||||
|
|||||||
@@ -21,9 +21,7 @@
|
|||||||
package com.vitorpamplona.amethyst.model
|
package com.vitorpamplona.amethyst.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import com.vitorpamplona.amethyst.commons.data.LargeCache
|
import com.vitorpamplona.amethyst.commons.data.LargeCache
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
|
||||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||||
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
||||||
@@ -43,6 +41,7 @@ import com.vitorpamplona.quartz.nip28PublicChat.base.ChannelData
|
|||||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||||
import com.vitorpamplona.quartz.utils.Hex
|
import com.vitorpamplona.quartz.utils.Hex
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class PublicChatChannel(
|
class PublicChatChannel(
|
||||||
@@ -93,7 +92,7 @@ class PublicChatChannel(
|
|||||||
return info.picture ?: super.profilePicture()
|
return info.picture ?: super.profilePicture()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun anyNameStartsWith(prefix: String): Boolean = listOfNotNull(info.name, info.about).filter { it.contains(prefix, true) }.isNotEmpty()
|
override fun anyNameStartsWith(prefix: String): Boolean = listOfNotNull(info.name, info.about).any { it.contains(prefix, true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
@@ -182,7 +181,7 @@ abstract class Channel(
|
|||||||
this.creator = creator
|
this.creator = creator
|
||||||
this.updatedMetadataAt = updatedAt
|
this.updatedMetadataAt = updatedAt
|
||||||
|
|
||||||
live.invalidateData()
|
flow.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -227,7 +226,7 @@ abstract class Channel(
|
|||||||
abstract fun anyNameStartsWith(prefix: String): Boolean
|
abstract fun anyNameStartsWith(prefix: String): Boolean
|
||||||
|
|
||||||
// Observers line up here.
|
// Observers line up here.
|
||||||
val live: ChannelLiveData = ChannelLiveData(this)
|
val flow: ChannelFlow = ChannelFlow(this)
|
||||||
|
|
||||||
fun pruneOldMessages(): Set<Note> {
|
fun pruneOldMessages(): Set<Note> {
|
||||||
val important =
|
val important =
|
||||||
@@ -257,22 +256,24 @@ abstract class Channel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChannelLiveData(
|
class ChannelFlow(
|
||||||
val channel: Channel,
|
val channel: Channel,
|
||||||
) : LiveData<ChannelState>(ChannelState(channel)) {
|
) {
|
||||||
// Refreshes observers in batches.
|
// Refreshes observers in batches.
|
||||||
private val bundler = BundledUpdate(300, Dispatchers.IO)
|
private val bundler = BundledUpdate(300, Dispatchers.IO)
|
||||||
|
val stateFlow = MutableStateFlow(ChannelState(channel))
|
||||||
|
|
||||||
fun invalidateData() {
|
fun invalidateData() {
|
||||||
checkNotInMainThread()
|
|
||||||
|
|
||||||
bundler.invalidate {
|
bundler.invalidate {
|
||||||
checkNotInMainThread()
|
stateFlow.emit(ChannelState(channel))
|
||||||
if (hasActiveObservers()) {
|
|
||||||
postValue(ChannelState(channel))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun destroy() {
|
||||||
|
bundler.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasObservers() = stateFlow.subscriptionCount.value > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChannelState(
|
class ChannelState(
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class Chatroom {
|
|||||||
} else {
|
} else {
|
||||||
// Old messages, keep the last one.
|
// Old messages, keep the last one.
|
||||||
sorted.take(1).toSet()
|
sorted.take(1).toSet()
|
||||||
} + sorted.filter { it.liveSet?.isInUse() ?: false } + sorted.filter { it.event !is PrivateDmEvent }
|
} + sorted.filter { it.flowSet?.isInUse() ?: false } + sorted.filter { it.event !is PrivateDmEvent }
|
||||||
|
|
||||||
val toRemove = roomMessages.minus(toKeep)
|
val toRemove = roomMessages.minus(toKeep)
|
||||||
roomMessages = toKeep
|
roomMessages = toKeep
|
||||||
|
|||||||
@@ -938,7 +938,7 @@ object LocalCache : ILocalCache {
|
|||||||
if (event.createdAt > (note.createdAt() ?: 0)) {
|
if (event.createdAt > (note.createdAt() ?: 0)) {
|
||||||
note.loadEvent(event, author, emptyList())
|
note.loadEvent(event, author, emptyList())
|
||||||
|
|
||||||
author.liveSet?.statuses?.invalidateData()
|
author.flowSet?.statuses?.invalidateData()
|
||||||
|
|
||||||
refreshObservers(note)
|
refreshObservers(note)
|
||||||
}
|
}
|
||||||
@@ -963,7 +963,7 @@ object LocalCache : ILocalCache {
|
|||||||
|
|
||||||
if (version.event == null) {
|
if (version.event == null) {
|
||||||
version.loadEvent(event, author, emptyList())
|
version.loadEvent(event, author, emptyList())
|
||||||
version.liveSet?.ots?.invalidateData()
|
version.flowSet?.ots?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshObservers(version)
|
refreshObservers(version)
|
||||||
@@ -1245,7 +1245,6 @@ object LocalCache : ILocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteNote.clearFlow()
|
deleteNote.clearFlow()
|
||||||
deleteNote.clearLive()
|
|
||||||
|
|
||||||
notes.remove(deleteNote.idHex)
|
notes.remove(deleteNote.idHex)
|
||||||
}
|
}
|
||||||
@@ -1259,7 +1258,6 @@ object LocalCache : ILocalCache {
|
|||||||
deleteWraps(noteEvent)
|
deleteWraps(noteEvent)
|
||||||
}
|
}
|
||||||
it.clearFlow()
|
it.clearFlow()
|
||||||
it.clearLive()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
notes.remove(it.id)
|
notes.remove(it.id)
|
||||||
@@ -1379,7 +1377,7 @@ object LocalCache : ILocalCache {
|
|||||||
|
|
||||||
mentions.forEach {
|
mentions.forEach {
|
||||||
// doesn't add to reports, but triggers recounts
|
// doesn't add to reports, but triggers recounts
|
||||||
it.liveSet?.reports?.invalidateData()
|
it.flowSet?.reports?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1625,7 +1623,7 @@ object LocalCache : ILocalCache {
|
|||||||
checkGetOrCreateNote(it.eventId)?.let { editedNote ->
|
checkGetOrCreateNote(it.eventId)?.let { editedNote ->
|
||||||
modificationCache.remove(editedNote.idHex)
|
modificationCache.remove(editedNote.idHex)
|
||||||
// must update list of Notes to quickly update the user.
|
// must update list of Notes to quickly update the user.
|
||||||
editedNote.liveSet?.edits?.invalidateData()
|
editedNote.flowSet?.edits?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2056,10 +2054,6 @@ object LocalCache : ILocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun cleanObservers() {
|
fun cleanObservers() {
|
||||||
notes.forEach { _, it -> it.clearLive() }
|
|
||||||
addressables.forEach { _, it -> it.clearLive() }
|
|
||||||
users.forEach { _, it -> it.clearLive() }
|
|
||||||
|
|
||||||
notes.forEach { _, it -> it.clearFlow() }
|
notes.forEach { _, it -> it.clearFlow() }
|
||||||
addressables.forEach { _, it -> it.clearFlow() }
|
addressables.forEach { _, it -> it.clearFlow() }
|
||||||
users.forEach { _, it -> it.clearFlow() }
|
users.forEach { _, it -> it.clearFlow() }
|
||||||
@@ -2213,8 +2207,8 @@ object LocalCache : ILocalCache {
|
|||||||
note.event is ReportEvent ||
|
note.event is ReportEvent ||
|
||||||
note.event is GenericRepostEvent
|
note.event is GenericRepostEvent
|
||||||
) &&
|
) &&
|
||||||
note.replyTo?.any { it.liveSet?.isInUse() == true } != true &&
|
note.replyTo?.any { it.flowSet?.isInUse() == true } != true &&
|
||||||
note.liveSet?.isInUse() != true &&
|
note.flowSet?.isInUse() != true &&
|
||||||
// don't delete if observing.
|
// don't delete if observing.
|
||||||
note.author?.pubkeyHex !in
|
note.author?.pubkeyHex !in
|
||||||
accounts &&
|
accounts &&
|
||||||
@@ -2278,7 +2272,6 @@ object LocalCache : ILocalCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
note.clearFlow()
|
note.clearFlow()
|
||||||
note.clearLive()
|
|
||||||
|
|
||||||
notes.remove(note.idHex)
|
notes.remove(note.idHex)
|
||||||
}
|
}
|
||||||
@@ -2313,12 +2306,10 @@ object LocalCache : ILocalCache {
|
|||||||
val childrenToBeRemoved = mutableListOf<Note>()
|
val childrenToBeRemoved = mutableListOf<Note>()
|
||||||
|
|
||||||
val toBeRemoved =
|
val toBeRemoved =
|
||||||
account.liveHiddenUsers.value
|
account.flowHiddenUsers.value.hiddenUsers
|
||||||
?.hiddenUsers
|
.map { userHex ->
|
||||||
?.map { userHex ->
|
|
||||||
(notes.filter { _, it -> it.event?.pubKey == userHex } + addressables.filter { _, it -> it.event?.pubKey == userHex }).toSet()
|
(notes.filter { _, it -> it.event?.pubKey == userHex } + addressables.filter { _, it -> it.event?.pubKey == userHex }).toSet()
|
||||||
}?.flatten()
|
}.flatten()
|
||||||
?: emptyList()
|
|
||||||
|
|
||||||
toBeRemoved.forEach {
|
toBeRemoved.forEach {
|
||||||
removeFromCache(it)
|
removeFromCache(it)
|
||||||
@@ -2337,7 +2328,7 @@ object LocalCache : ILocalCache {
|
|||||||
users.forEach { _, user ->
|
users.forEach { _, user ->
|
||||||
if (
|
if (
|
||||||
user.pubkeyHex !in loggedIn &&
|
user.pubkeyHex !in loggedIn &&
|
||||||
(user.liveSet == null || user.liveSet?.isInUse() == false) &&
|
(user.flowSet == null || user.flowSet?.isInUse() == false) &&
|
||||||
user.latestContactList != null
|
user.latestContactList != null
|
||||||
) {
|
) {
|
||||||
user.latestContactList = null
|
user.latestContactList = null
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import com.vitorpamplona.amethyst.launchAndWaitAll
|
import com.vitorpamplona.amethyst.launchAndWaitAll
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.service.firstFullCharOrEmoji
|
import com.vitorpamplona.amethyst.service.firstFullCharOrEmoji
|
||||||
@@ -228,29 +227,36 @@ open class Note(
|
|||||||
this.author = author
|
this.author = author
|
||||||
this.replyTo = replyTo
|
this.replyTo = replyTo
|
||||||
|
|
||||||
liveSet?.metadata?.invalidateData()
|
|
||||||
flowSet?.metadata?.invalidateData()
|
flowSet?.metadata?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasZapsBoostsOrReactions(): Boolean = reactions.isNotEmpty() || zaps.isNotEmpty() || boosts.isNotEmpty()
|
||||||
|
|
||||||
|
fun countReactions(): Int {
|
||||||
|
var total = 0
|
||||||
|
reactions.forEach { total += it.value.size }
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
fun addReply(note: Note) {
|
fun addReply(note: Note) {
|
||||||
if (note !in replies) {
|
if (note !in replies) {
|
||||||
replies = replies + note
|
replies = replies + note
|
||||||
liveSet?.replies?.invalidateData()
|
flowSet?.replies?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeReply(note: Note) {
|
fun removeReply(note: Note) {
|
||||||
if (note in replies) {
|
if (note in replies) {
|
||||||
replies = replies - note
|
replies = replies - note
|
||||||
liveSet?.replies?.invalidateData()
|
flowSet?.replies?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeBoost(note: Note) {
|
fun removeBoost(note: Note) {
|
||||||
if (note in boosts) {
|
if (note in boosts) {
|
||||||
boosts = boosts - note
|
boosts = boosts - note
|
||||||
liveSet?.boosts?.invalidateData()
|
flowSet?.boosts?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,13 +287,11 @@ open class Note(
|
|||||||
relays = listOf<RelayBriefInfoCache.RelayBriefInfo>()
|
relays = listOf<RelayBriefInfoCache.RelayBriefInfo>()
|
||||||
lastReactionsDownloadTime = emptyMap()
|
lastReactionsDownloadTime = emptyMap()
|
||||||
|
|
||||||
if (repliesChanged) liveSet?.replies?.invalidateData()
|
if (repliesChanged) flowSet?.replies?.invalidateData()
|
||||||
if (reactionsChanged) liveSet?.reactions?.invalidateData()
|
if (reactionsChanged) flowSet?.reactions?.invalidateData()
|
||||||
if (boostsChanged) liveSet?.boosts?.invalidateData()
|
if (boostsChanged) flowSet?.boosts?.invalidateData()
|
||||||
if (reportsChanged) {
|
if (reportsChanged) flowSet?.reports?.invalidateData()
|
||||||
flowSet?.reports?.invalidateData()
|
if (zapsChanged) flowSet?.zaps?.invalidateData()
|
||||||
}
|
|
||||||
if (zapsChanged) liveSet?.zaps?.invalidateData()
|
|
||||||
|
|
||||||
return toBeRemoved
|
return toBeRemoved
|
||||||
}
|
}
|
||||||
@@ -306,7 +310,7 @@ open class Note(
|
|||||||
reactions = reactions + Pair(reaction, newList)
|
reactions = reactions + Pair(reaction, newList)
|
||||||
}
|
}
|
||||||
|
|
||||||
liveSet?.reactions?.invalidateData()
|
flowSet?.reactions?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,28 +331,28 @@ open class Note(
|
|||||||
if (zaps[note] != null) {
|
if (zaps[note] != null) {
|
||||||
zaps = zaps.minus(note)
|
zaps = zaps.minus(note)
|
||||||
updateZapTotal()
|
updateZapTotal()
|
||||||
liveSet?.zaps?.invalidateData()
|
flowSet?.zaps?.invalidateData()
|
||||||
} else if (zaps.containsValue(note)) {
|
} else if (zaps.containsValue(note)) {
|
||||||
zaps = zaps.filterValues { it != note }
|
zaps = zaps.filterValues { it != note }
|
||||||
updateZapTotal()
|
updateZapTotal()
|
||||||
liveSet?.zaps?.invalidateData()
|
flowSet?.zaps?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeZapPayment(note: Note) {
|
fun removeZapPayment(note: Note) {
|
||||||
if (zapPayments.containsKey(note)) {
|
if (zapPayments.containsKey(note)) {
|
||||||
zapPayments = zapPayments.minus(note)
|
zapPayments = zapPayments.minus(note)
|
||||||
liveSet?.zaps?.invalidateData()
|
flowSet?.zaps?.invalidateData()
|
||||||
} else if (zapPayments.containsValue(note)) {
|
} else if (zapPayments.containsValue(note)) {
|
||||||
zapPayments = zapPayments.filterValues { it != note }
|
zapPayments = zapPayments.filterValues { it != note }
|
||||||
liveSet?.zaps?.invalidateData()
|
flowSet?.zaps?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addBoost(note: Note) {
|
fun addBoost(note: Note) {
|
||||||
if (note !in boosts) {
|
if (note !in boosts) {
|
||||||
boosts = boosts + note
|
boosts = boosts + note
|
||||||
liveSet?.boosts?.invalidateData()
|
flowSet?.boosts?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +379,7 @@ open class Note(
|
|||||||
val inserted = innerAddZap(zapRequest, zap)
|
val inserted = innerAddZap(zapRequest, zap)
|
||||||
if (inserted) {
|
if (inserted) {
|
||||||
updateZapTotal()
|
updateZapTotal()
|
||||||
liveSet?.zaps?.invalidateData()
|
flowSet?.zaps?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -401,7 +405,7 @@ open class Note(
|
|||||||
if (zapPayments[zapPaymentRequest] == null) {
|
if (zapPayments[zapPaymentRequest] == null) {
|
||||||
val inserted = innerAddZapPayment(zapPaymentRequest, zapPayment)
|
val inserted = innerAddZapPayment(zapPaymentRequest, zapPayment)
|
||||||
if (inserted) {
|
if (inserted) {
|
||||||
liveSet?.zaps?.invalidateData()
|
flowSet?.zaps?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -413,10 +417,10 @@ open class Note(
|
|||||||
val listOfAuthors = reactions[reaction]
|
val listOfAuthors = reactions[reaction]
|
||||||
if (listOfAuthors == null) {
|
if (listOfAuthors == null) {
|
||||||
reactions = reactions + Pair(reaction, listOf(note))
|
reactions = reactions + Pair(reaction, listOf(note))
|
||||||
liveSet?.reactions?.invalidateData()
|
flowSet?.reactions?.invalidateData()
|
||||||
} else if (!listOfAuthors.contains(note)) {
|
} else if (!listOfAuthors.contains(note)) {
|
||||||
reactions = reactions + Pair(reaction, listOfAuthors + note)
|
reactions = reactions + Pair(reaction, listOfAuthors + note)
|
||||||
liveSet?.reactions?.invalidateData()
|
flowSet?.reactions?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -866,36 +870,8 @@ open class Note(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var liveSet: NoteLiveSet? = null
|
|
||||||
var flowSet: NoteFlowSet? = null
|
var flowSet: NoteFlowSet? = null
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun createOrDestroyLiveSync(create: Boolean) {
|
|
||||||
if (create) {
|
|
||||||
if (liveSet == null) {
|
|
||||||
liveSet = NoteLiveSet(this)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (liveSet != null && liveSet?.isInUse() == false) {
|
|
||||||
liveSet?.destroy()
|
|
||||||
liveSet = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun live(): NoteLiveSet {
|
|
||||||
if (liveSet == null) {
|
|
||||||
createOrDestroyLiveSync(true)
|
|
||||||
}
|
|
||||||
return liveSet!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearLive() {
|
|
||||||
if (liveSet != null && liveSet?.isInUse() == false) {
|
|
||||||
createOrDestroyLiveSync(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun createOrDestroyFlowSync(create: Boolean) {
|
fun createOrDestroyFlowSync(create: Boolean) {
|
||||||
if (create) {
|
if (create) {
|
||||||
@@ -970,6 +946,12 @@ class NoteFlowSet(
|
|||||||
val metadata = NoteBundledRefresherFlow(u)
|
val metadata = NoteBundledRefresherFlow(u)
|
||||||
val reports = NoteBundledRefresherFlow(u)
|
val reports = NoteBundledRefresherFlow(u)
|
||||||
val relays = NoteBundledRefresherFlow(u)
|
val relays = NoteBundledRefresherFlow(u)
|
||||||
|
val reactions = NoteBundledRefresherFlow(u)
|
||||||
|
val boosts = NoteBundledRefresherFlow(u)
|
||||||
|
val replies = NoteBundledRefresherFlow(u)
|
||||||
|
val zaps = NoteBundledRefresherFlow(u)
|
||||||
|
val ots = NoteBundledRefresherFlow(u)
|
||||||
|
val edits = NoteBundledRefresherFlow(u)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun author() =
|
fun author() =
|
||||||
@@ -980,33 +962,11 @@ class NoteFlowSet(
|
|||||||
?.stateFlow ?: MutableStateFlow(null)
|
?.stateFlow ?: MutableStateFlow(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isInUse(): Boolean =
|
|
||||||
metadata.stateFlow.subscriptionCount.value > 0 ||
|
|
||||||
reports.stateFlow.subscriptionCount.value > 0 ||
|
|
||||||
relays.stateFlow.subscriptionCount.value > 0
|
|
||||||
|
|
||||||
fun destroy() {
|
|
||||||
metadata.destroy()
|
|
||||||
reports.destroy()
|
|
||||||
relays.destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
class NoteLiveSet(
|
|
||||||
u: Note,
|
|
||||||
) {
|
|
||||||
// Observers line up here.
|
|
||||||
val metadata = NoteBundledRefresherLiveData(u)
|
|
||||||
val reactions = NoteBundledRefresherLiveData(u)
|
|
||||||
val boosts = NoteBundledRefresherLiveData(u)
|
|
||||||
val replies = NoteBundledRefresherLiveData(u)
|
|
||||||
val zaps = NoteBundledRefresherLiveData(u)
|
|
||||||
val ots = NoteBundledRefresherLiveData(u)
|
|
||||||
val edits = NoteBundledRefresherLiveData(u)
|
|
||||||
|
|
||||||
fun isInUse(): Boolean =
|
fun isInUse(): Boolean =
|
||||||
metadata.hasObservers() ||
|
metadata.hasObservers() ||
|
||||||
|
reports.hasObservers() ||
|
||||||
|
relays.hasObservers() ||
|
||||||
|
metadata.hasObservers() ||
|
||||||
reactions.hasObservers() ||
|
reactions.hasObservers() ||
|
||||||
boosts.hasObservers() ||
|
boosts.hasObservers() ||
|
||||||
replies.hasObservers() ||
|
replies.hasObservers() ||
|
||||||
@@ -1016,6 +976,8 @@ class NoteLiveSet(
|
|||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
metadata.destroy()
|
metadata.destroy()
|
||||||
|
reports.destroy()
|
||||||
|
relays.destroy()
|
||||||
reactions.destroy()
|
reactions.destroy()
|
||||||
boosts.destroy()
|
boosts.destroy()
|
||||||
replies.destroy()
|
replies.destroy()
|
||||||
@@ -1039,38 +1001,15 @@ class NoteBundledRefresherFlow(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun invalidateData() {
|
fun invalidateData() {
|
||||||
checkNotInMainThread()
|
|
||||||
|
|
||||||
bundler.invalidate {
|
bundler.invalidate {
|
||||||
checkNotInMainThread()
|
|
||||||
|
|
||||||
stateFlow.emit(NoteState(note))
|
stateFlow.emit(NoteState(note))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasObservers() = stateFlow.subscriptionCount.value > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Immutable
|
||||||
class NoteBundledRefresherLiveData(
|
class NoteState(
|
||||||
val note: Note,
|
|
||||||
) : LiveData<NoteState>(NoteState(note)) {
|
|
||||||
// Refreshes observers in batches.
|
|
||||||
private val bundler = BundledUpdate(500, Dispatchers.IO)
|
|
||||||
|
|
||||||
fun destroy() {
|
|
||||||
bundler.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun invalidateData() {
|
|
||||||
checkNotInMainThread()
|
|
||||||
|
|
||||||
bundler.invalidate {
|
|
||||||
checkNotInMainThread()
|
|
||||||
|
|
||||||
postValue(NoteState(note))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Immutable class NoteState(
|
|
||||||
val note: Note,
|
val note: Note,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
||||||
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
||||||
@@ -125,7 +124,7 @@ class User(
|
|||||||
if (event.id == latestBookmarkList?.id) return
|
if (event.id == latestBookmarkList?.id) return
|
||||||
|
|
||||||
latestBookmarkList = event
|
latestBookmarkList = event
|
||||||
liveSet?.bookmarks?.invalidateData()
|
flowSet?.bookmarks?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearEOSE() {
|
fun clearEOSE() {
|
||||||
@@ -139,7 +138,6 @@ class User(
|
|||||||
latestContactList = event
|
latestContactList = event
|
||||||
|
|
||||||
// Update following of the current user
|
// Update following of the current user
|
||||||
liveSet?.follows?.invalidateData()
|
|
||||||
flowSet?.follows?.invalidateData()
|
flowSet?.follows?.invalidateData()
|
||||||
|
|
||||||
// Update Followers of the past user list
|
// Update Followers of the past user list
|
||||||
@@ -147,19 +145,18 @@ class User(
|
|||||||
(oldContactListEvent)?.unverifiedFollowKeySet()?.forEach {
|
(oldContactListEvent)?.unverifiedFollowKeySet()?.forEach {
|
||||||
LocalCache
|
LocalCache
|
||||||
.getUserIfExists(it)
|
.getUserIfExists(it)
|
||||||
?.liveSet
|
?.flowSet
|
||||||
?.followers
|
?.followers
|
||||||
?.invalidateData()
|
?.invalidateData()
|
||||||
}
|
}
|
||||||
(latestContactList)?.unverifiedFollowKeySet()?.forEach {
|
(latestContactList)?.unverifiedFollowKeySet()?.forEach {
|
||||||
LocalCache
|
LocalCache
|
||||||
.getUserIfExists(it)
|
.getUserIfExists(it)
|
||||||
?.liveSet
|
?.flowSet
|
||||||
?.followers
|
?.followers
|
||||||
?.invalidateData()
|
?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
liveSet?.relays?.invalidateData()
|
|
||||||
flowSet?.relays?.invalidateData()
|
flowSet?.relays?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,10 +166,10 @@ class User(
|
|||||||
val reportsBy = reports[author]
|
val reportsBy = reports[author]
|
||||||
if (reportsBy == null) {
|
if (reportsBy == null) {
|
||||||
reports = reports + Pair(author, setOf(note))
|
reports = reports + Pair(author, setOf(note))
|
||||||
liveSet?.reports?.invalidateData()
|
flowSet?.reports?.invalidateData()
|
||||||
} else if (!reportsBy.contains(note)) {
|
} else if (!reportsBy.contains(note)) {
|
||||||
reports = reports + Pair(author, reportsBy + note)
|
reports = reports + Pair(author, reportsBy + note)
|
||||||
liveSet?.reports?.invalidateData()
|
flowSet?.reports?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +179,7 @@ class User(
|
|||||||
if (reports[author]?.contains(deleteNote) == true) {
|
if (reports[author]?.contains(deleteNote) == true) {
|
||||||
reports[author]?.let {
|
reports[author]?.let {
|
||||||
reports = reports + Pair(author, it.minus(deleteNote))
|
reports = reports + Pair(author, it.minus(deleteNote))
|
||||||
liveSet?.reports?.invalidateData()
|
flowSet?.reports?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,17 +190,17 @@ class User(
|
|||||||
) {
|
) {
|
||||||
if (zaps[zapRequest] == null) {
|
if (zaps[zapRequest] == null) {
|
||||||
zaps = zaps + Pair(zapRequest, zap)
|
zaps = zaps + Pair(zapRequest, zap)
|
||||||
liveSet?.zaps?.invalidateData()
|
flowSet?.zaps?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeZap(zapRequestOrZapEvent: Note) {
|
fun removeZap(zapRequestOrZapEvent: Note) {
|
||||||
if (zaps.containsKey(zapRequestOrZapEvent)) {
|
if (zaps.containsKey(zapRequestOrZapEvent)) {
|
||||||
zaps = zaps.minus(zapRequestOrZapEvent)
|
zaps = zaps.minus(zapRequestOrZapEvent)
|
||||||
liveSet?.zaps?.invalidateData()
|
flowSet?.zaps?.invalidateData()
|
||||||
} else if (zaps.containsValue(zapRequestOrZapEvent)) {
|
} else if (zaps.containsValue(zapRequestOrZapEvent)) {
|
||||||
zaps = zaps.filter { it.value != zapRequestOrZapEvent }
|
zaps = zaps.filter { it.value != zapRequestOrZapEvent }
|
||||||
liveSet?.zaps?.invalidateData()
|
flowSet?.zaps?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +253,7 @@ class User(
|
|||||||
val privateChatroom = getOrCreatePrivateChatroom(room)
|
val privateChatroom = getOrCreatePrivateChatroom(room)
|
||||||
if (msg !in privateChatroom.roomMessages) {
|
if (msg !in privateChatroom.roomMessages) {
|
||||||
privateChatroom.addMessageSync(msg)
|
privateChatroom.addMessageSync(msg)
|
||||||
liveSet?.messages?.invalidateData()
|
flowSet?.messages?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +264,7 @@ class User(
|
|||||||
val privateChatroom = getOrCreatePrivateChatroom(user)
|
val privateChatroom = getOrCreatePrivateChatroom(user)
|
||||||
if (msg !in privateChatroom.roomMessages) {
|
if (msg !in privateChatroom.roomMessages) {
|
||||||
privateChatroom.addMessageSync(msg)
|
privateChatroom.addMessageSync(msg)
|
||||||
liveSet?.messages?.invalidateData()
|
flowSet?.messages?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +281,7 @@ class User(
|
|||||||
val privateChatroom = getOrCreatePrivateChatroom(user)
|
val privateChatroom = getOrCreatePrivateChatroom(user)
|
||||||
if (msg in privateChatroom.roomMessages) {
|
if (msg in privateChatroom.roomMessages) {
|
||||||
privateChatroom.removeMessageSync(msg)
|
privateChatroom.removeMessageSync(msg)
|
||||||
liveSet?.messages?.invalidateData()
|
flowSet?.messages?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +293,7 @@ class User(
|
|||||||
val privateChatroom = getOrCreatePrivateChatroom(room)
|
val privateChatroom = getOrCreatePrivateChatroom(room)
|
||||||
if (msg in privateChatroom.roomMessages) {
|
if (msg in privateChatroom.roomMessages) {
|
||||||
privateChatroom.removeMessageSync(msg)
|
privateChatroom.removeMessageSync(msg)
|
||||||
liveSet?.messages?.invalidateData()
|
flowSet?.messages?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +311,7 @@ class User(
|
|||||||
here.counter++
|
here.counter++
|
||||||
}
|
}
|
||||||
|
|
||||||
liveSet?.relayInfo?.invalidateData()
|
flowSet?.relayInfo?.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateUserInfo(
|
fun updateUserInfo(
|
||||||
@@ -334,7 +331,6 @@ class User(
|
|||||||
}
|
}
|
||||||
|
|
||||||
flowSet?.metadata?.invalidateData()
|
flowSet?.metadata?.invalidateData()
|
||||||
liveSet?.metadata?.invalidateData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isFollowing(user: User): Boolean = latestContactList?.isTaggedUser(user.pubkeyHex) ?: false
|
fun isFollowing(user: User): Boolean = latestContactList?.isTaggedUser(user.pubkeyHex) ?: false
|
||||||
@@ -398,36 +394,8 @@ class User(
|
|||||||
|
|
||||||
fun anyNameStartsWith(username: String): Boolean = info?.anyNameStartsWith(username) ?: false
|
fun anyNameStartsWith(username: String): Boolean = info?.anyNameStartsWith(username) ?: false
|
||||||
|
|
||||||
var liveSet: UserLiveSet? = null
|
|
||||||
var flowSet: UserFlowSet? = null
|
var flowSet: UserFlowSet? = null
|
||||||
|
|
||||||
fun live(): UserLiveSet {
|
|
||||||
if (liveSet == null) {
|
|
||||||
createOrDestroyLiveSync(true)
|
|
||||||
}
|
|
||||||
return liveSet!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearLive() {
|
|
||||||
if (liveSet != null && liveSet?.isInUse() == false) {
|
|
||||||
createOrDestroyLiveSync(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun createOrDestroyLiveSync(create: Boolean) {
|
|
||||||
if (create) {
|
|
||||||
if (liveSet == null) {
|
|
||||||
liveSet = UserLiveSet(this)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (liveSet != null && liveSet?.isInUse() == false) {
|
|
||||||
liveSet?.destroy()
|
|
||||||
liveSet = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun createOrDestroyFlowSync(create: Boolean) {
|
fun createOrDestroyFlowSync(create: Boolean) {
|
||||||
if (create) {
|
if (create) {
|
||||||
@@ -464,43 +432,21 @@ class UserFlowSet(
|
|||||||
val metadata = UserBundledRefresherFlow(u)
|
val metadata = UserBundledRefresherFlow(u)
|
||||||
val follows = UserBundledRefresherFlow(u)
|
val follows = UserBundledRefresherFlow(u)
|
||||||
val relays = UserBundledRefresherFlow(u)
|
val relays = UserBundledRefresherFlow(u)
|
||||||
|
val followers = UserBundledRefresherFlow(u)
|
||||||
fun isInUse(): Boolean =
|
val reports = UserBundledRefresherFlow(u)
|
||||||
metadata.stateFlow.subscriptionCount.value > 0 ||
|
val messages = UserBundledRefresherFlow(u)
|
||||||
relays.stateFlow.subscriptionCount.value > 0 ||
|
val relayInfo = UserBundledRefresherFlow(u)
|
||||||
follows.stateFlow.subscriptionCount.value > 0
|
val zaps = UserBundledRefresherFlow(u)
|
||||||
|
val bookmarks = UserBundledRefresherFlow(u)
|
||||||
fun destroy() {
|
val statuses = UserBundledRefresherFlow(u)
|
||||||
metadata.destroy()
|
|
||||||
relays.destroy()
|
|
||||||
follows.destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
class UserLiveSet(
|
|
||||||
u: User,
|
|
||||||
) {
|
|
||||||
val metadata = UserBundledRefresherLiveData(u)
|
|
||||||
|
|
||||||
// UI Observers line up here.
|
|
||||||
val follows = UserBundledRefresherLiveData(u)
|
|
||||||
val followers = UserBundledRefresherLiveData(u)
|
|
||||||
val reports = UserBundledRefresherLiveData(u)
|
|
||||||
val messages = UserBundledRefresherLiveData(u)
|
|
||||||
val relays = UserBundledRefresherLiveData(u)
|
|
||||||
val relayInfo = UserBundledRefresherLiveData(u)
|
|
||||||
val zaps = UserBundledRefresherLiveData(u)
|
|
||||||
val bookmarks = UserBundledRefresherLiveData(u)
|
|
||||||
val statuses = UserBundledRefresherLiveData(u)
|
|
||||||
|
|
||||||
fun isInUse(): Boolean =
|
fun isInUse(): Boolean =
|
||||||
metadata.hasObservers() ||
|
metadata.hasObservers() ||
|
||||||
|
relays.hasObservers() ||
|
||||||
follows.hasObservers() ||
|
follows.hasObservers() ||
|
||||||
followers.hasObservers() ||
|
followers.hasObservers() ||
|
||||||
reports.hasObservers() ||
|
reports.hasObservers() ||
|
||||||
messages.hasObservers() ||
|
messages.hasObservers() ||
|
||||||
relays.hasObservers() ||
|
|
||||||
relayInfo.hasObservers() ||
|
relayInfo.hasObservers() ||
|
||||||
zaps.hasObservers() ||
|
zaps.hasObservers() ||
|
||||||
bookmarks.hasObservers() ||
|
bookmarks.hasObservers() ||
|
||||||
@@ -508,11 +454,11 @@ class UserLiveSet(
|
|||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
metadata.destroy()
|
metadata.destroy()
|
||||||
|
relays.destroy()
|
||||||
follows.destroy()
|
follows.destroy()
|
||||||
followers.destroy()
|
followers.destroy()
|
||||||
reports.destroy()
|
reports.destroy()
|
||||||
messages.destroy()
|
messages.destroy()
|
||||||
relays.destroy()
|
|
||||||
relayInfo.destroy()
|
relayInfo.destroy()
|
||||||
zaps.destroy()
|
zaps.destroy()
|
||||||
bookmarks.destroy()
|
bookmarks.destroy()
|
||||||
@@ -527,27 +473,6 @@ data class RelayInfo(
|
|||||||
var counter: Long,
|
var counter: Long,
|
||||||
)
|
)
|
||||||
|
|
||||||
class UserBundledRefresherLiveData(
|
|
||||||
val user: User,
|
|
||||||
) : LiveData<UserState>(UserState(user)) {
|
|
||||||
// Refreshes observers in batches.
|
|
||||||
private val bundler = BundledUpdate(500, Dispatchers.IO)
|
|
||||||
|
|
||||||
fun destroy() {
|
|
||||||
bundler.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun invalidateData() {
|
|
||||||
checkNotInMainThread()
|
|
||||||
|
|
||||||
bundler.invalidate {
|
|
||||||
checkNotInMainThread()
|
|
||||||
|
|
||||||
postValue(UserState(user))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class UserBundledRefresherFlow(
|
class UserBundledRefresherFlow(
|
||||||
val user: User,
|
val user: User,
|
||||||
@@ -569,8 +494,11 @@ class UserBundledRefresherFlow(
|
|||||||
stateFlow.emit(UserState(user))
|
stateFlow.emit(UserState(user))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasObservers() = stateFlow.subscriptionCount.value > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable class UserState(
|
@Immutable
|
||||||
|
class UserState(
|
||||||
val user: User,
|
val user: User,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service
|
package com.vitorpamplona.amethyst.service
|
||||||
|
|
||||||
import android.content.ContentProviderOperation.newCall
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import com.vitorpamplona.quartz.nip11RelayInfo.Nip11RelayInformation
|
import com.vitorpamplona.quartz.nip11RelayInfo.Nip11RelayInformation
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
package com.vitorpamplona.amethyst.service.location
|
package com.vitorpamplona.amethyst.service.location
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import coil3.util.CoilUtils.result
|
|
||||||
import com.fonfon.kgeohash.GeoHash
|
import com.fonfon.kgeohash.GeoHash
|
||||||
import com.fonfon.kgeohash.toGeoHash
|
import com.fonfon.kgeohash.toGeoHash
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeohashPrecision
|
import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeohashPrecision
|
||||||
|
|||||||
-2
@@ -27,9 +27,7 @@ import android.content.Context.RECEIVER_EXPORTED
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.LiveFolders.INTENT
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.ContextCompat.registerReceiver
|
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|||||||
+8
-12
@@ -30,12 +30,10 @@ import com.vitorpamplona.amethyst.service.relayClient.searchCommand.MutableQuery
|
|||||||
fun <T> KeyDataSourceSubscription(
|
fun <T> KeyDataSourceSubscription(
|
||||||
state: T,
|
state: T,
|
||||||
dataSource: QueryBasedSubscriptionOrchestrator<T>,
|
dataSource: QueryBasedSubscriptionOrchestrator<T>,
|
||||||
) {
|
) = DisposableEffect(state) {
|
||||||
DisposableEffect(state) {
|
dataSource.subscribe(state)
|
||||||
dataSource.subscribe(state)
|
onDispose {
|
||||||
onDispose {
|
dataSource.unsubscribe(state)
|
||||||
dataSource.unsubscribe(state)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,11 +41,9 @@ fun <T> KeyDataSourceSubscription(
|
|||||||
fun <T : MutableQueryState> KeyDataSourceSubscription(
|
fun <T : MutableQueryState> KeyDataSourceSubscription(
|
||||||
state: T,
|
state: T,
|
||||||
dataSource: MutableQueryBasedSubscriptionOrchestrator<T>,
|
dataSource: MutableQueryBasedSubscriptionOrchestrator<T>,
|
||||||
) {
|
) = DisposableEffect(state) {
|
||||||
DisposableEffect(state) {
|
dataSource.subscribe(state)
|
||||||
dataSource.subscribe(state)
|
onDispose {
|
||||||
onDispose {
|
dataSource.unsubscribe(state)
|
||||||
dataSource.unsubscribe(state)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -25,7 +25,6 @@ import com.vitorpamplona.amethyst.isDebug
|
|||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||||
import com.vitorpamplona.ammolite.relays.datasources.RelayAuthenticator
|
import com.vitorpamplona.ammolite.relays.datasources.RelayAuthenticator
|
||||||
import kotlin.collections.forEach
|
|
||||||
|
|
||||||
class ScreenAuthAccount(
|
class ScreenAuthAccount(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.authCommand.model
|
package com.vitorpamplona.amethyst.service.relayClient.authCommand.model
|
||||||
|
|
||||||
import androidx.lifecycle.AtomicReference
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
|
||||||
class ListWithUniqueSetCache<T, U>(
|
class ListWithUniqueSetCache<T, U>(
|
||||||
val key: (T) -> U,
|
val key: (T) -> U,
|
||||||
|
|||||||
-10
@@ -20,8 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.vitorpamplona.amethyst.isDebug
|
|
||||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||||
import com.vitorpamplona.ammolite.relays.datasources.SubscriptionOrchestrator
|
import com.vitorpamplona.ammolite.relays.datasources.SubscriptionOrchestrator
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
@@ -48,10 +46,6 @@ abstract class QueryBasedSubscriptionOrchestrator<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
invalidateFilters()
|
invalidateFilters()
|
||||||
|
|
||||||
if (isDebug) {
|
|
||||||
Log.d(this::class.simpleName, "Watch $query (${queries.size} queries)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is called by main. Keep it really fast.
|
// This is called by main. Keep it really fast.
|
||||||
@@ -65,10 +59,6 @@ abstract class QueryBasedSubscriptionOrchestrator<T>(
|
|||||||
if (queries.isEmpty()) {
|
if (queries.isEmpty()) {
|
||||||
stop()
|
stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDebug) {
|
|
||||||
Log.d(this::class.simpleName, "Unwatch $query (${queries.size} queries)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forEachSubscriber(action: (T) -> Unit) {
|
fun forEachSubscriber(action: (T) -> Unit) {
|
||||||
|
|||||||
+62
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeAccountIsHiddenWord(
|
||||||
|
account: Account,
|
||||||
|
word: String,
|
||||||
|
): State<Boolean> {
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(account, word) {
|
||||||
|
account.flowHiddenUsers
|
||||||
|
.map { word in it.hiddenWords }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeAccountIsHiddenUser(
|
||||||
|
account: Account,
|
||||||
|
user: User,
|
||||||
|
): State<Boolean> {
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(account, user) {
|
||||||
|
account.flowHiddenUsers
|
||||||
|
.map { it.hiddenUsers.contains(user.pubkeyHex) || it.spammers.contains(user.pubkeyHex) }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(account.isHidden(user))
|
||||||
|
}
|
||||||
-2
@@ -31,8 +31,6 @@ import com.vitorpamplona.ammolite.relays.TypedFilter
|
|||||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent
|
||||||
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent
|
||||||
import kotlin.collections.filter
|
|
||||||
import kotlin.collections.mapNotNull
|
|
||||||
|
|
||||||
// This allows multiple screen to be listening to tags, even the same tag
|
// This allows multiple screen to be listening to tags, even the same tag
|
||||||
class ChannelFinderQueryState(
|
class ChannelFinderQueryState(
|
||||||
|
|||||||
+41
-2
@@ -22,13 +22,52 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.ChannelState
|
import com.vitorpamplona.amethyst.model.ChannelState
|
||||||
|
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||||
|
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun observeChannel(baseChannel: Channel): State<ChannelState?> {
|
fun observeChannel(baseChannel: Channel): State<ChannelState?> {
|
||||||
ChannelFinderFilterAssemblerSubscription(baseChannel)
|
ChannelFinderFilterAssemblerSubscription(baseChannel)
|
||||||
|
|
||||||
return baseChannel.live.observeAsState()
|
return baseChannel.flow.stateFlow.collectAsStateWithLifecycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeChannelPicture(baseChannel: Channel): State<String?> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
ChannelFinderFilterAssemblerSubscription(baseChannel)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(baseChannel) {
|
||||||
|
baseChannel
|
||||||
|
.flow.stateFlow
|
||||||
|
.mapLatest { it.channel.profilePicture() }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(baseChannel.profilePicture())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeChannelInfo(baseChannel: LiveActivitiesChannel): State<LiveActivitiesEvent?> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
ChannelFinderFilterAssemblerSubscription(baseChannel)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(baseChannel) {
|
||||||
|
baseChannel
|
||||||
|
.flow.stateFlow
|
||||||
|
.mapLatest { (it.channel as? LiveActivitiesChannel)?.info }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(baseChannel.info)
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -46,7 +46,6 @@ import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
|||||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
||||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
|
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
|
||||||
import kotlin.collections.mapNotNullTo
|
|
||||||
|
|
||||||
// This allows multiple screen to be listening to tags, even the same tag
|
// This allows multiple screen to be listening to tags, even the same tag
|
||||||
class EventFinderQueryState(
|
class EventFinderQueryState(
|
||||||
|
|||||||
+140
-75
@@ -22,22 +22,30 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.remember
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.map
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.NoteState
|
import com.vitorpamplona.amethyst.model.NoteState
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.ui.note.combineWith
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
import kotlinx.coroutines.flow.sample
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun observeNote(note: Note): State<NoteState?> {
|
fun observeNote(note: Note): State<NoteState> {
|
||||||
// Subscribe in the relay for changes in this note.
|
// Subscribe in the relay for changes in this note.
|
||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note.live().metadata.observeAsState()
|
return note
|
||||||
|
.flow()
|
||||||
|
.metadata.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -46,11 +54,15 @@ fun <T : Event> observeNoteEvent(note: Note): State<T?> {
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note
|
val flow =
|
||||||
.live()
|
remember(note) {
|
||||||
.metadata
|
note
|
||||||
.map { it.note.event as? T? }
|
.flow()
|
||||||
.observeAsState(note.event as? T?)
|
.metadata.stateFlow
|
||||||
|
.mapLatest { it.note.event as? T? }
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(note.event as? T?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -61,13 +73,18 @@ fun <T> observeNoteAndMap(
|
|||||||
// Subscribe in the relay for changes in this note.
|
// Subscribe in the relay for changes in this note.
|
||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
|
val flow =
|
||||||
|
remember(note) {
|
||||||
|
note
|
||||||
|
.flow()
|
||||||
|
.metadata.stateFlow
|
||||||
|
.mapLatest { map(it.note) }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note
|
return flow.collectAsStateWithLifecycle(map(note))
|
||||||
.live()
|
|
||||||
.metadata
|
|
||||||
.map { map(it.note) }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.observeAsState(map(note))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -79,14 +96,18 @@ fun <T, U> observeNoteEventAndMap(
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note
|
val flow =
|
||||||
.live()
|
remember(note) {
|
||||||
.metadata
|
note
|
||||||
.map { (it.note.event as? T)?.let { map(it) } }
|
.flow()
|
||||||
.distinctUntilChanged()
|
.metadata.stateFlow
|
||||||
.observeAsState(
|
.mapLatest { (it.note.event as? T)?.let { map(it) } }
|
||||||
(note.event as? T)?.let { map(it) },
|
.distinctUntilChanged()
|
||||||
)
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
return flow.collectAsStateWithLifecycle((note.event as? T)?.let { map(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -95,12 +116,16 @@ fun observeNoteHasEvent(note: Note): State<Boolean> {
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note
|
val flow =
|
||||||
.live()
|
remember(note) {
|
||||||
.metadata
|
note
|
||||||
.map { it.note.event != null }
|
.flow()
|
||||||
.distinctUntilChanged()
|
.metadata.stateFlow
|
||||||
.observeAsState(note.event != null)
|
.mapLatest { it.note.event != null }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(note.event != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -109,7 +134,10 @@ fun observeNoteReplies(note: Note): State<NoteState?> {
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note.live().replies.observeAsState()
|
return note
|
||||||
|
.flow()
|
||||||
|
.replies.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -118,12 +146,17 @@ fun observeNoteReplyCount(note: Note): State<Int> {
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note
|
val flow =
|
||||||
.live()
|
remember(note) {
|
||||||
.replies
|
note
|
||||||
.map { it.note.reactions.size }
|
.flow()
|
||||||
.distinctUntilChanged()
|
.reactions.stateFlow
|
||||||
.observeAsState(note.reactions.size)
|
.mapLatest { it.note.replies.size }
|
||||||
|
.sample(1000)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(note.replies.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -132,7 +165,10 @@ fun observeNoteReactions(note: Note): State<NoteState?> {
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note.live().reactions.observeAsState()
|
return note
|
||||||
|
.flow()
|
||||||
|
.reactions.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -141,15 +177,19 @@ fun observeNoteReactionCount(note: Note): State<Int> {
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note
|
val flow =
|
||||||
.live()
|
remember(note) {
|
||||||
.reactions
|
note
|
||||||
.map {
|
.flow()
|
||||||
var total = 0
|
.reactions.stateFlow
|
||||||
it.note.reactions.forEach { total += it.value.size }
|
.mapLatest { it.note.countReactions() }
|
||||||
total
|
.sample(1000)
|
||||||
}.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.observeAsState(0)
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
return flow.collectAsStateWithLifecycle(note.countReactions())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -158,7 +198,10 @@ fun observeNoteZaps(note: Note): State<NoteState?> {
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note.live().zaps.observeAsState()
|
return note
|
||||||
|
.flow()
|
||||||
|
.zaps.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -167,7 +210,10 @@ fun observeNoteReposts(note: Note): State<NoteState?> {
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note.live().boosts.observeAsState()
|
return note
|
||||||
|
.flow()
|
||||||
|
.boosts.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -179,12 +225,17 @@ fun observeNoteRepostsBy(
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note
|
val flow =
|
||||||
.live()
|
remember(note) {
|
||||||
.boosts
|
note
|
||||||
.map { it.note.isBoostedBy(user) }
|
.flow()
|
||||||
.distinctUntilChanged()
|
.boosts.stateFlow
|
||||||
.observeAsState(note.isBoostedBy(user))
|
.mapLatest { it.note.isBoostedBy(user) }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(note.isBoostedBy(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -193,12 +244,17 @@ fun observeNoteRepostCount(note: Note): State<Int> {
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note
|
val flow =
|
||||||
.live()
|
remember(note) {
|
||||||
.boosts
|
note
|
||||||
.map { it.note.boosts.size }
|
.flow()
|
||||||
.distinctUntilChanged()
|
.boosts.stateFlow
|
||||||
.observeAsState(note.boosts.size)
|
.sample(1000)
|
||||||
|
.mapLatest { note.boosts.size }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(note.boosts.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -207,17 +263,18 @@ fun observeNoteReferences(note: Note): State<Boolean> {
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note
|
val flow =
|
||||||
.live()
|
remember(note) {
|
||||||
.zaps
|
combine(
|
||||||
.combineWith(note.live().boosts, note.live().reactions) { zapState, boostState, reactionState ->
|
note.flow().zaps.stateFlow,
|
||||||
zapState?.note?.zaps?.isNotEmpty() == true ||
|
note.flow().boosts.stateFlow,
|
||||||
boostState?.note?.boosts?.isNotEmpty() == true ||
|
note.flow().reactions.stateFlow,
|
||||||
reactionState?.note?.reactions?.isNotEmpty() == true
|
) { zapState, boostState, reactionState ->
|
||||||
}.distinctUntilChanged()
|
zapState.note.hasZapsBoostsOrReactions()
|
||||||
.observeAsState(
|
}.distinctUntilChanged()
|
||||||
note.zaps.isNotEmpty() || note.boosts.isNotEmpty() || note.reactions.isNotEmpty(),
|
}
|
||||||
)
|
|
||||||
|
return flow.collectAsStateWithLifecycle(note.hasZapsBoostsOrReactions())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -226,7 +283,11 @@ fun observeNoteOts(note: Note): State<NoteState?> {
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note.live().ots.observeAsState()
|
return note
|
||||||
|
.flow()
|
||||||
|
.ots
|
||||||
|
.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -235,5 +296,9 @@ fun observeNoteEdits(note: Note): State<NoteState?> {
|
|||||||
EventFinderFilterAssemblerSubscription(note)
|
EventFinderFilterAssemblerSubscription(note)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return note.live().edits.observeAsState()
|
return note
|
||||||
|
.flow()
|
||||||
|
.edits
|
||||||
|
.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
}
|
}
|
||||||
|
|||||||
+478
-47
@@ -22,12 +22,26 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.remember
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.map
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.UserState
|
import com.vitorpamplona.amethyst.model.UserState
|
||||||
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.events.isTaggedEvent
|
||||||
|
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||||
|
import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
import kotlinx.coroutines.flow.sample
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun observeUser(user: User): State<UserState?> {
|
fun observeUser(user: User): State<UserState?> {
|
||||||
@@ -35,7 +49,10 @@ fun observeUser(user: User): State<UserState?> {
|
|||||||
UserFinderFilterAssemblerSubscription(user)
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return user.live().metadata.observeAsState()
|
return user
|
||||||
|
.flow()
|
||||||
|
.metadata.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -43,13 +60,17 @@ fun observeUserName(user: User): State<String> {
|
|||||||
// Subscribe in the relay for changes in the metadata of this user.
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
UserFinderFilterAssemblerSubscription(user)
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.metadata.stateFlow
|
||||||
|
.mapLatest { it.user.toBestDisplayName() }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return user
|
return flow.collectAsStateWithLifecycle(user.toBestDisplayName())
|
||||||
.live()
|
|
||||||
.metadata
|
|
||||||
.map { it.user.toBestDisplayName() }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.observeAsState(user.toBestDisplayName())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -57,13 +78,17 @@ fun observeUserNip05(user: User): State<String?> {
|
|||||||
// Subscribe in the relay for changes in the metadata of this user.
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
UserFinderFilterAssemblerSubscription(user)
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.metadata.stateFlow
|
||||||
|
.mapLatest { it.user.info?.nip05 }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return user
|
return flow.collectAsStateWithLifecycle(user.info?.nip05)
|
||||||
.live()
|
|
||||||
.metadata
|
|
||||||
.map { it.user.info?.nip05 }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.observeAsState(user.info?.nip05)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -72,12 +97,16 @@ fun observeUserAboutMe(user: User): State<String> {
|
|||||||
UserFinderFilterAssemblerSubscription(user)
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return user
|
val flow =
|
||||||
.live()
|
remember(user) {
|
||||||
.metadata
|
user
|
||||||
.map { it.user.info?.about ?: "" }
|
.flow()
|
||||||
.distinctUntilChanged()
|
.metadata.stateFlow
|
||||||
.observeAsState(user.info?.about ?: "")
|
.mapLatest { it.user.info?.about ?: "" }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(user.info?.about ?: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -86,26 +115,34 @@ fun observeUserInfo(user: User): State<UserMetadata?> {
|
|||||||
UserFinderFilterAssemblerSubscription(user)
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return user
|
val flow =
|
||||||
.live()
|
remember(user) {
|
||||||
.metadata
|
user
|
||||||
.map { it.user.info }
|
.flow()
|
||||||
.distinctUntilChanged()
|
.metadata.stateFlow
|
||||||
.observeAsState(user.info)
|
.mapLatest { it.user.info }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(user.info)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun observeUserBanner(user: User): State<String> {
|
fun observeUserBanner(user: User): State<String?> {
|
||||||
// Subscribe in the relay for changes in the metadata of this user.
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
UserFinderFilterAssemblerSubscription(user)
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return user
|
val flow =
|
||||||
.live()
|
remember(user) {
|
||||||
.metadata
|
user
|
||||||
.map { it.user.info?.banner ?: "" }
|
.flow()
|
||||||
.distinctUntilChanged()
|
.metadata.stateFlow
|
||||||
.observeAsState(user.info?.banner ?: "")
|
.mapLatest { it.user.info?.banner }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(user.info?.banner)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -114,12 +151,16 @@ fun observeUserPicture(user: User): State<String?> {
|
|||||||
UserFinderFilterAssemblerSubscription(user)
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return user
|
val flow =
|
||||||
.live()
|
remember(user) {
|
||||||
.metadata
|
user
|
||||||
.map { it.user.info?.picture }
|
.flow()
|
||||||
.distinctUntilChanged()
|
.metadata.stateFlow
|
||||||
.observeAsState(user.info?.picture)
|
.mapLatest { it.user.info?.picture }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(user.info?.picture)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -128,10 +169,400 @@ fun observeUserShortName(user: User): State<String> {
|
|||||||
UserFinderFilterAssemblerSubscription(user)
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
// Subscribe in the LocalCache for changes that arrive in the device
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
return user
|
val flow =
|
||||||
.live()
|
remember(user) {
|
||||||
.metadata
|
user
|
||||||
.map { it.user.toBestShortFirstName() }
|
.flow()
|
||||||
.distinctUntilChanged()
|
.metadata.stateFlow
|
||||||
.observeAsState(user.toBestShortFirstName())
|
.mapLatest { it.user.toBestShortFirstName() }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(user.toBestShortFirstName())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserFollows(user: User): State<UserState?> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
return user
|
||||||
|
.flow()
|
||||||
|
.follows.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserFollowCount(user: User): State<Int> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.followers.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
userState.user.transientFollowCount() ?: 0
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserTagFollows(user: User): State<Int> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.follows.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
userState.user.latestContactList?.countFollowTags() ?: 0
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserBookmarks(user: User): State<UserState?> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
return user
|
||||||
|
.flow()
|
||||||
|
.bookmarks.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserBookmarkCount(user: User): State<Int> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.followers.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
userState.user.latestBookmarkList?.countBookmarks() ?: 0
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserFollowers(user: User): State<UserState?> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
return user
|
||||||
|
.flow()
|
||||||
|
.followers.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserFollowerCount(user: User): State<Int> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.followers.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
userState.user.transientFollowerCount()
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserIsFollowing(
|
||||||
|
user1: User,
|
||||||
|
user2: User,
|
||||||
|
): State<Boolean> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user1)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user1) {
|
||||||
|
user1
|
||||||
|
.flow()
|
||||||
|
.follows.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
userState.user.isFollowing(user2)
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(user1.isFollowing(user2))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserIsFollowingHashtag(
|
||||||
|
user: User,
|
||||||
|
hashtag: String,
|
||||||
|
): State<Boolean> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.follows.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
userState.user.isFollowingHashtag(hashtag)
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(user.isFollowingHashtag(hashtag))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserIsFollowingGeohash(
|
||||||
|
user: User,
|
||||||
|
geohash: String,
|
||||||
|
): State<Boolean> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.follows.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
userState.user.isFollowingGeohash(geohash)
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(user.isFollowingGeohash(geohash))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserIsFollowingChannel(
|
||||||
|
user: User,
|
||||||
|
channel: Channel,
|
||||||
|
): State<Boolean> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.follows.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
userState.user.latestContactList?.isTaggedEvent(channel.idHex) ?: false
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(user.latestContactList?.isTaggedEvent(channel.idHex) ?: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserZaps(user: User): State<UserState?> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
return user
|
||||||
|
.flow()
|
||||||
|
.zaps.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserZapAmount(user: User): State<BigDecimal> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.zaps.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
userState.user.zappedAmount()
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(BigDecimal.ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserReports(user: User): State<UserState?> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
return user
|
||||||
|
.flow()
|
||||||
|
.reports.stateFlow
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserReportCount(user: User): State<Int> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.reports
|
||||||
|
.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
userState.user.reports.values
|
||||||
|
.sumOf { it.size }
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserStatuses(user: User): State<ImmutableList<AddressableNote>> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.statuses
|
||||||
|
.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
LocalCache.findStatusesForUser(userState.user)
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(persistentListOf())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserRelayIntoList(
|
||||||
|
user: User,
|
||||||
|
relayUrl: String,
|
||||||
|
): State<Boolean> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.relayInfo
|
||||||
|
.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
userState.user.latestContactList
|
||||||
|
?.relays()
|
||||||
|
?.none { it.key == relayUrl } == true
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserRoomSubject(
|
||||||
|
user: User,
|
||||||
|
room: ChatroomKey,
|
||||||
|
): State<String?> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
user
|
||||||
|
.flow()
|
||||||
|
.messages
|
||||||
|
.stateFlow
|
||||||
|
.sample(1000)
|
||||||
|
.mapLatest { userState ->
|
||||||
|
userState.user.privateChatrooms[room]?.subject
|
||||||
|
}.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(user.privateChatrooms[room]?.subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class RelayUsage(
|
||||||
|
val relays: List<String> = emptyList(),
|
||||||
|
val userRelayList: List<String> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun observeUserRelaysUsing(user: User): State<RelayUsage> {
|
||||||
|
// Subscribe in the relay for changes in the metadata of this user.
|
||||||
|
UserFinderFilterAssemblerSubscription(user)
|
||||||
|
|
||||||
|
// Subscribe in the LocalCache for changes that arrive in the device
|
||||||
|
val flow =
|
||||||
|
remember(user) {
|
||||||
|
combine(user.flow().relays.stateFlow, user.flow().relayInfo.stateFlow) { relays, relayInfo ->
|
||||||
|
val userRelaysBeingUsed = relays.user.relaysBeingUsed.map { it.key }
|
||||||
|
val currentUserRelays =
|
||||||
|
relayInfo.user.latestContactList
|
||||||
|
?.relays()
|
||||||
|
?.map { RelayUrlFormatter.normalize(it.key) } ?: emptyList()
|
||||||
|
|
||||||
|
RelayUsage(userRelaysBeingUsed, currentUserRelays)
|
||||||
|
}.sample(1000)
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flow.collectAsStateWithLifecycle(RelayUsage())
|
||||||
}
|
}
|
||||||
|
|||||||
-10
@@ -20,8 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.searchCommand
|
package com.vitorpamplona.amethyst.service.relayClient.searchCommand
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.vitorpamplona.amethyst.isDebug
|
|
||||||
import com.vitorpamplona.ammolite.relays.NostrClient
|
import com.vitorpamplona.ammolite.relays.NostrClient
|
||||||
import com.vitorpamplona.ammolite.relays.datasources.SubscriptionOrchestrator
|
import com.vitorpamplona.ammolite.relays.datasources.SubscriptionOrchestrator
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -64,10 +62,6 @@ abstract class MutableQueryBasedSubscriptionOrchestrator<T : MutableQueryState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
invalidateFilters()
|
invalidateFilters()
|
||||||
|
|
||||||
if (isDebug) {
|
|
||||||
Log.d(this::class.simpleName, "Watch $query (${queries.size} queries)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is called by main. Keep it really fast.
|
// This is called by main. Keep it really fast.
|
||||||
@@ -82,10 +76,6 @@ abstract class MutableQueryBasedSubscriptionOrchestrator<T : MutableQueryState>(
|
|||||||
if (queries.isEmpty()) {
|
if (queries.isEmpty()) {
|
||||||
stop()
|
stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDebug) {
|
|
||||||
Log.d(this::class.simpleName, "Unwatch $query (${queries.size} queries)")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun forEachSubscriber(action: (T) -> Unit) {
|
fun forEachSubscriber(action: (T) -> Unit) {
|
||||||
|
|||||||
-1
@@ -67,7 +67,6 @@ import com.vitorpamplona.quartz.utils.Hex
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlin.collections.flatten
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class SearchQueryState(
|
class SearchQueryState(
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import androidx.annotation.RequiresApi
|
|||||||
import androidx.core.net.toFile
|
import androidx.core.net.toFile
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk.PICTURES_SUBDIRECTORY
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Callback
|
import okhttp3.Callback
|
||||||
|
|||||||
-1
@@ -26,7 +26,6 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import coil3.util.CoilUtils.result
|
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
|||||||
@@ -20,8 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
import android.R.attr.maxLines
|
|
||||||
import android.R.attr.onClick
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
|||||||
-1
@@ -50,7 +50,6 @@ import androidx.compose.material3.ButtonDefaults
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedTextFieldDefaults.contentPadding
|
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
|||||||
-1
@@ -29,7 +29,6 @@ import androidx.compose.material3.Button
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.window.core.layout.WindowHeightSizeClass
|
import androidx.window.core.layout.WindowHeightSizeClass
|
||||||
import androidx.window.core.layout.WindowSizeClass
|
import androidx.window.core.layout.WindowSizeClass
|
||||||
import androidx.window.core.layout.WindowWidthSizeClass
|
import androidx.window.core.layout.WindowWidthSizeClass
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.util.DeviceUtils.screenOrientationIsLocked
|
||||||
|
|
||||||
object DeviceUtils {
|
object DeviceUtils {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,22 +22,9 @@ package com.vitorpamplona.amethyst.ui.dal
|
|||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.Card
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.Card
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
|
||||||
|
|
||||||
val DefaultFeedOrder: Comparator<Note> =
|
val DefaultFeedOrder: Comparator<Note> =
|
||||||
compareByDescending<Note>
|
compareByDescending<Note> { it.event?.createdAt }.thenBy { it.idHex }
|
||||||
{
|
|
||||||
val noteEvent = it.event
|
|
||||||
if (noteEvent == null) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
if (noteEvent is Event) {
|
|
||||||
noteEvent.createdAt
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.thenBy { it.idHex }
|
|
||||||
|
|
||||||
val DefaultFeedOrderCard: Comparator<Card> =
|
val DefaultFeedOrderCard: Comparator<Card> =
|
||||||
compareByDescending<Card> { it.createdAt() }.thenBy { it.id() }
|
compareByDescending<Card> { it.createdAt() }.thenBy { it.id() }
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -91,6 +90,7 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserFollowerCount
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
|
||||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.MediaServersListView
|
import com.vitorpamplona.amethyst.ui.actions.mediaServers.MediaServersListView
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
@@ -381,18 +381,12 @@ private fun FollowingAndFollowerCounts(
|
|||||||
baseAccountUser: Account,
|
baseAccountUser: Account,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val followingCount = baseAccountUser.liveKind3Follows.collectAsStateWithLifecycle()
|
|
||||||
var followerCount by remember { mutableStateOf("--") }
|
|
||||||
|
|
||||||
WatchFollower(baseAccountUser = baseAccountUser) { newFollower ->
|
|
||||||
if (followerCount != newFollower) {
|
|
||||||
followerCount = newFollower
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = drawerSpacing.clickable(onClick = onClick),
|
modifier = drawerSpacing.clickable(onClick = onClick),
|
||||||
) {
|
) {
|
||||||
|
val followingCount = baseAccountUser.liveKind3Follows.collectAsStateWithLifecycle()
|
||||||
|
val followerCount by observeUserFollowerCount(baseAccountUser.userProfile())
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text =
|
text =
|
||||||
followingCount.value.authors.size
|
followingCount.value.authors.size
|
||||||
@@ -405,7 +399,7 @@ private fun FollowingAndFollowerCounts(
|
|||||||
Spacer(modifier = DoubleHorzSpacer)
|
Spacer(modifier = DoubleHorzSpacer)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = followerCount,
|
text = if (followerCount > 0) followerCount.toString() else "--",
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -413,22 +407,6 @@ private fun FollowingAndFollowerCounts(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun WatchFollower(
|
|
||||||
baseAccountUser: Account,
|
|
||||||
onReady: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
val accountUserFollowersState by baseAccountUser
|
|
||||||
.userProfile()
|
|
||||||
.live()
|
|
||||||
.followers
|
|
||||||
.observeAsState()
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = accountUserFollowersState) {
|
|
||||||
onReady(baseAccountUser.followerCount().toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ListContent(
|
fun ListContent(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.Size23dp
|
import com.vitorpamplona.amethyst.ui.theme.Size23dp
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlin.String
|
|
||||||
|
|
||||||
class BottomBarRoute(
|
class BottomBarRoute(
|
||||||
val route: Route,
|
val route: Route,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import android.R.attr.onClick
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -28,7 +27,6 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav.nav
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ import androidx.compose.material.icons.filled.PushPin
|
|||||||
import androidx.compose.material.icons.filled.Report
|
import androidx.compose.material.icons.filled.Report
|
||||||
import androidx.compose.material.icons.filled.Share
|
import androidx.compose.material.icons.filled.Share
|
||||||
import androidx.compose.material.icons.outlined.AddReaction
|
import androidx.compose.material.icons.outlined.AddReaction
|
||||||
import androidx.compose.material.icons.outlined.Bolt
|
|
||||||
import androidx.compose.material.icons.outlined.OpenInNew
|
|
||||||
import androidx.compose.material.icons.outlined.PlayCircle
|
import androidx.compose.material.icons.outlined.PlayCircle
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.note
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -33,12 +32,11 @@ import com.vitorpamplona.amethyst.model.Channel
|
|||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteOts
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteOts
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserStatuses
|
||||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -139,19 +137,9 @@ fun LoadStatuses(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
content: @Composable (ImmutableList<AddressableNote>) -> Unit,
|
content: @Composable (ImmutableList<AddressableNote>) -> Unit,
|
||||||
) {
|
) {
|
||||||
var statuses: ImmutableList<AddressableNote> by remember { mutableStateOf(persistentListOf()) }
|
val userStatuses by observeUserStatuses(user)
|
||||||
|
|
||||||
val userStatus by user.live().statuses.observeAsState()
|
content(userStatuses)
|
||||||
|
|
||||||
LaunchedEffect(key1 = userStatus) {
|
|
||||||
accountViewModel.findStatusesForUser(userStatus?.user ?: user) { newStatuses ->
|
|
||||||
if (!equalImmutableLists(statuses, newStatuses)) {
|
|
||||||
statuses = newStatuses
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
content(statuses)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+15
-17
@@ -54,11 +54,11 @@ import com.vitorpamplona.amethyst.model.User
|
|||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFinderFilterAssemblerSubscription
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserNip05
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserNip05
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserStatuses
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadStatuses
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.NIP05CheckingIcon
|
import com.vitorpamplona.amethyst.ui.note.NIP05CheckingIcon
|
||||||
import com.vitorpamplona.amethyst.ui.note.NIP05FailedVerification
|
import com.vitorpamplona.amethyst.ui.note.NIP05FailedVerification
|
||||||
import com.vitorpamplona.amethyst.ui.note.NIP05VerifiedIcon
|
import com.vitorpamplona.amethyst.ui.note.NIP05VerifiedIcon
|
||||||
@@ -136,23 +136,21 @@ fun ObserveDisplayNip05Status(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val nip05 by observeUserNip05(baseUser)
|
val nip05 by observeUserNip05(baseUser)
|
||||||
|
val statuses by observeUserStatuses(baseUser)
|
||||||
|
|
||||||
LoadStatuses(baseUser, accountViewModel) { statuses ->
|
CrossfadeIfEnabled(
|
||||||
CrossfadeIfEnabled(
|
targetState = nip05,
|
||||||
targetState = nip05,
|
modifier = columnModifier,
|
||||||
modifier = columnModifier,
|
accountViewModel = accountViewModel,
|
||||||
label = "ObserveDisplayNip05StatusCrossfade",
|
) {
|
||||||
accountViewModel = accountViewModel,
|
VerifyAndDisplayNIP05OrStatusLine(
|
||||||
) {
|
it,
|
||||||
VerifyAndDisplayNIP05OrStatusLine(
|
statuses,
|
||||||
it,
|
baseUser,
|
||||||
statuses,
|
columnModifier,
|
||||||
baseUser,
|
accountViewModel,
|
||||||
columnModifier,
|
nav,
|
||||||
accountViewModel,
|
)
|
||||||
nav,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import androidx.compose.runtime.MutableState
|
|||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -50,9 +49,6 @@ import androidx.compose.ui.graphics.compositeOver
|
|||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
|
||||||
import androidx.lifecycle.map
|
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.compose.produceCachedStateAsync
|
import com.vitorpamplona.amethyst.commons.compose.produceCachedStateAsync
|
||||||
import com.vitorpamplona.amethyst.logTime
|
import com.vitorpamplona.amethyst.logTime
|
||||||
@@ -60,7 +56,7 @@ import com.vitorpamplona.amethyst.model.AddressableNote
|
|||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannelPicture
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEdits
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEdits
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent
|
||||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||||
@@ -137,7 +133,6 @@ import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing10dp
|
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing10dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing5dp
|
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing5dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||||
@@ -1195,24 +1190,16 @@ private fun ChannelNotePicture(
|
|||||||
loadProfilePicture: Boolean,
|
loadProfilePicture: Boolean,
|
||||||
loadRobohash: Boolean,
|
loadRobohash: Boolean,
|
||||||
) {
|
) {
|
||||||
ChannelFinderFilterAssemblerSubscription(baseChannel, Amethyst.instance.sources.channelFinder)
|
val model by observeChannelPicture(baseChannel)
|
||||||
|
|
||||||
val model by
|
RobohashFallbackAsyncImage(
|
||||||
baseChannel.live
|
robot = baseChannel.idHex,
|
||||||
.map { it.channel.profilePicture() }
|
model = model,
|
||||||
.distinctUntilChanged()
|
contentDescription = stringRes(R.string.group_picture),
|
||||||
.observeAsState()
|
modifier = MaterialTheme.colorScheme.channelNotePictureModifier,
|
||||||
|
loadProfilePicture = loadProfilePicture,
|
||||||
Box(Size30Modifier) {
|
loadRobohash = loadRobohash,
|
||||||
RobohashFallbackAsyncImage(
|
)
|
||||||
robot = baseChannel.idHex,
|
|
||||||
model = model,
|
|
||||||
contentDescription = stringRes(R.string.group_picture),
|
|
||||||
modifier = MaterialTheme.colorScheme.channelNotePictureModifier,
|
|
||||||
loadProfilePicture = loadProfilePicture,
|
|
||||||
loadRobohash = loadRobohash,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -93,8 +93,6 @@ import androidx.compose.ui.unit.sp
|
|||||||
import androidx.compose.ui.window.Popup
|
import androidx.compose.ui.window.Popup
|
||||||
import androidx.compose.ui.window.PopupProperties
|
import androidx.compose.ui.window.PopupProperties
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.LiveData
|
|
||||||
import androidx.lifecycle.MediatorLiveData
|
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||||
@@ -405,28 +403,6 @@ private fun WatchReactionsZapsBoostsAndDisplayIfExists(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T, K, R> LiveData<T>.combineWith(
|
|
||||||
liveData1: LiveData<K>,
|
|
||||||
block: (T?, K?) -> R,
|
|
||||||
): LiveData<R> {
|
|
||||||
val result = MediatorLiveData<R>()
|
|
||||||
result.addSource(this) { result.value = block(this.value, liveData1.value) }
|
|
||||||
result.addSource(liveData1) { result.value = block(this.value, liveData1.value) }
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T, K, P, R> LiveData<T>.combineWith(
|
|
||||||
liveData1: LiveData<K>,
|
|
||||||
liveData2: LiveData<P>,
|
|
||||||
block: (T?, K?, P?) -> R,
|
|
||||||
): LiveData<R> {
|
|
||||||
val result = MediatorLiveData<R>()
|
|
||||||
result.addSource(this) { result.value = block(this.value, liveData1.value, liveData2.value) }
|
|
||||||
result.addSource(liveData1) { result.value = block(this.value, liveData1.value, liveData2.value) }
|
|
||||||
result.addSource(liveData2) { result.value = block(this.value, liveData1.value, liveData2.value) }
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderShowIndividualReactionsButton(
|
private fun RenderShowIndividualReactionsButton(
|
||||||
wantsToSeeReactions: MutableState<Boolean>,
|
wantsToSeeReactions: MutableState<Boolean>,
|
||||||
|
|||||||
@@ -223,11 +223,9 @@ class UpdateZapAmountViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun updateNIP47(uri: String) {
|
fun updateNIP47(uri: String) {
|
||||||
val contact = Nip47WalletConnect.parse(uri)
|
val contact = Nip47WalletConnect.parse(uri)
|
||||||
if (contact != null) {
|
walletConnectPubkey = TextFieldValue(contact.pubKeyHex)
|
||||||
walletConnectPubkey = TextFieldValue(contact.pubKeyHex)
|
walletConnectRelay = TextFieldValue(contact.relayUri)
|
||||||
walletConnectRelay = TextFieldValue(contact.relayUri ?: "")
|
walletConnectSecret = TextFieldValue(contact.secret ?: "")
|
||||||
walletConnectSecret = TextFieldValue(contact.secret ?: "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -43,15 +42,16 @@ import androidx.compose.ui.unit.sp
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenUser
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserAboutMe
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserAboutMe
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowing
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.FollowButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.FollowButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.UnfollowButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.UnfollowButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.ShowUserButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.ShowUserButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.WatchIsHiddenUser
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.ZapReqResponse
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.ZapReqResponse
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
import com.vitorpamplona.amethyst.ui.theme.Size55dp
|
||||||
@@ -161,12 +161,11 @@ fun UserActionOptions(
|
|||||||
baseAuthor: User,
|
baseAuthor: User,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
WatchIsHiddenUser(baseAuthor, accountViewModel) { isHidden ->
|
val isHidden by observeAccountIsHiddenUser(accountViewModel.account, baseAuthor)
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
ShowUserButton { accountViewModel.show(baseAuthor) }
|
ShowUserButton { accountViewModel.show(baseAuthor) }
|
||||||
} else {
|
} else {
|
||||||
ShowFollowingOrUnfollowingButton(baseAuthor, accountViewModel)
|
ShowFollowingOrUnfollowingButton(baseAuthor, accountViewModel)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,24 +174,9 @@ fun ShowFollowingOrUnfollowingButton(
|
|||||||
baseAuthor: User,
|
baseAuthor: User,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
var isFollowing by remember { mutableStateOf(false) }
|
var isFollowing = observeUserIsFollowing(accountViewModel.account.userProfile(), baseAuthor)
|
||||||
val accountFollowsState by accountViewModel.account
|
|
||||||
.userProfile()
|
|
||||||
.live()
|
|
||||||
.follows
|
|
||||||
.observeAsState()
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = accountFollowsState) {
|
if (isFollowing.value) {
|
||||||
launch(Dispatchers.Default) {
|
|
||||||
val newShowFollowingMark = accountFollowsState?.user?.isFollowing(baseAuthor) == true
|
|
||||||
|
|
||||||
if (newShowFollowingMark != isFollowing) {
|
|
||||||
isFollowing = newShowFollowingMark
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFollowing) {
|
|
||||||
UnfollowButton {
|
UnfollowButton {
|
||||||
if (!accountViewModel.isWriteable()) {
|
if (!accountViewModel.isWriteable()) {
|
||||||
accountViewModel.toastManager.toast(
|
accountViewModel.toastManager.toast(
|
||||||
|
|||||||
-1
@@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|||||||
-1
@@ -29,7 +29,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TextFieldDefaults
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
|||||||
-1
@@ -27,7 +27,6 @@ import androidx.compose.foundation.layout.width
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ShowChart
|
import androidx.compose.material.icons.automirrored.filled.ShowChart
|
||||||
import androidx.compose.material.icons.filled.Bolt
|
import androidx.compose.material.icons.filled.Bolt
|
||||||
import androidx.compose.material.icons.filled.ShowChart
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
|||||||
-5
@@ -23,12 +23,7 @@ package com.vitorpamplona.amethyst.ui.note.creators.zapsplits
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import kotlin.collections.all
|
|
||||||
import kotlin.collections.getOrNull
|
|
||||||
import kotlin.collections.lastIndex
|
|
||||||
import kotlin.compareTo
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.text.toDouble
|
|
||||||
|
|
||||||
class SplitBuilder<T> {
|
class SplitBuilder<T> {
|
||||||
var items: List<SplitItem<T>> by mutableStateOf(emptyList())
|
var items: List<SplitItem<T>> by mutableStateOf(emptyList())
|
||||||
|
|||||||
+13
-5
@@ -50,7 +50,7 @@ fun DefaultImageHeader(
|
|||||||
) {
|
) {
|
||||||
WatchAuthor(baseNote = note) {
|
WatchAuthor(baseNote = note) {
|
||||||
Box {
|
Box {
|
||||||
BannerImage(it)
|
BannerImage(it, Modifier.fillMaxWidth().heightIn(max = 200.dp))
|
||||||
|
|
||||||
Box(authorNotePictureForImageHeader.align(Alignment.BottomStart)) {
|
Box(authorNotePictureForImageHeader.align(Alignment.BottomStart)) {
|
||||||
BaseUserPicture(it, Size55dp, accountViewModel, Modifier)
|
BaseUserPicture(it, Size55dp, accountViewModel, Modifier)
|
||||||
@@ -62,11 +62,19 @@ fun DefaultImageHeader(
|
|||||||
@Composable
|
@Composable
|
||||||
fun BannerImage(
|
fun BannerImage(
|
||||||
author: User,
|
author: User,
|
||||||
imageModifier: Modifier = Modifier.fillMaxWidth().heightIn(max = 200.dp),
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val banner by observeUserBanner(author)
|
val banner by observeUserBanner(author)
|
||||||
|
|
||||||
if (banner.isNotBlank()) {
|
BannerImage(banner, modifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BannerImage(
|
||||||
|
banner: String?,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
if (!banner.isNullOrBlank()) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = banner,
|
model = banner,
|
||||||
contentDescription =
|
contentDescription =
|
||||||
@@ -75,7 +83,7 @@ fun BannerImage(
|
|||||||
banner,
|
banner,
|
||||||
),
|
),
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = imageModifier,
|
modifier = modifier,
|
||||||
placeholder = painterResource(R.drawable.profile_banner),
|
placeholder = painterResource(R.drawable.profile_banner),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -83,7 +91,7 @@ fun BannerImage(
|
|||||||
painter = painterResource(R.drawable.profile_banner),
|
painter = painterResource(R.drawable.profile_banner),
|
||||||
contentDescription = stringRes(R.string.profile_banner),
|
contentDescription = stringRes(R.string.profile_banner),
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = imageModifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.elements
|
package com.vitorpamplona.amethyst.ui.note.elements
|
||||||
|
|
||||||
import android.R.attr.maxLines
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.elements
|
package com.vitorpamplona.amethyst.ui.note.elements
|
||||||
|
|
||||||
import android.R.attr.maxLines
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import androidx.compose.runtime.Immutable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -42,6 +41,8 @@ import androidx.core.content.ContextCompat
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserBookmarks
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserFollows
|
||||||
import com.vitorpamplona.amethyst.ui.actions.EditPostView
|
import com.vitorpamplona.amethyst.ui.actions.EditPostView
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
||||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||||
@@ -59,6 +60,7 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
|||||||
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
|
import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MoreOptionsButton(
|
fun MoreOptionsButton(
|
||||||
@@ -380,22 +382,12 @@ fun WatchBookmarksFollowsAndAccount(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
onNew: (DropDownParams) -> Unit,
|
onNew: (DropDownParams) -> Unit,
|
||||||
) {
|
) {
|
||||||
val followState by accountViewModel
|
val followState by observeUserFollows(accountViewModel.userProfile())
|
||||||
.userProfile()
|
val bookmarkState by observeUserBookmarks(accountViewModel.userProfile())
|
||||||
.live()
|
val showSensitiveContent by accountViewModel.showSensitiveContent().collectAsStateWithLifecycle()
|
||||||
.follows
|
|
||||||
.observeAsState()
|
|
||||||
val bookmarkState by accountViewModel
|
|
||||||
.userProfile()
|
|
||||||
.live()
|
|
||||||
.bookmarks
|
|
||||||
.observeAsState()
|
|
||||||
val showSensitiveContent by accountViewModel
|
|
||||||
.showSensitiveContent()
|
|
||||||
.collectAsStateWithLifecycle()
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = followState, key2 = bookmarkState, key3 = showSensitiveContent) {
|
LaunchedEffect(key1 = followState, key2 = bookmarkState, key3 = showSensitiveContent) {
|
||||||
launch(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
accountViewModel.isInPrivateBookmarks(note) {
|
accountViewModel.isInPrivateBookmarks(note) {
|
||||||
val newState =
|
val newState =
|
||||||
DropDownParams(
|
DropDownParams(
|
||||||
|
|||||||
@@ -212,9 +212,7 @@ fun LongCommunityHeader(
|
|||||||
|
|
||||||
if (participants != null) {
|
if (participants != null) {
|
||||||
accountViewModel.loadParticipants(participants) { newParticipantUsers ->
|
accountViewModel.loadParticipants(participants) { newParticipantUsers ->
|
||||||
if (
|
if (!equalImmutableLists(newParticipantUsers, participantUsers)) {
|
||||||
newParticipantUsers != null && !equalImmutableLists(newParticipantUsers, participantUsers)
|
|
||||||
) {
|
|
||||||
participantUsers = newParticipantUsers
|
participantUsers = newParticipantUsers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -45,6 +43,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserRelayIntoList
|
||||||
import com.vitorpamplona.amethyst.ui.components.ShowMoreButton
|
import com.vitorpamplona.amethyst.ui.components.ShowMoreButton
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
@@ -279,21 +278,7 @@ private fun RelayOptionsAction(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val userStateRelayInfo by accountViewModel.account
|
val isCurrentlyOnTheUsersList by observeUserRelayIntoList(accountViewModel.userProfile(), relay)
|
||||||
.userProfile()
|
|
||||||
.live()
|
|
||||||
.relayInfo
|
|
||||||
.observeAsState()
|
|
||||||
val isCurrentlyOnTheUsersList by
|
|
||||||
remember(userStateRelayInfo) {
|
|
||||||
derivedStateOf {
|
|
||||||
userStateRelayInfo
|
|
||||||
?.user
|
|
||||||
?.latestContactList
|
|
||||||
?.relays()
|
|
||||||
?.none { it.key == relay } == true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCurrentlyOnTheUsersList) {
|
if (isCurrentlyOnTheUsersList) {
|
||||||
AddRelayButton {
|
AddRelayButton {
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
|||||||
+2
-2
@@ -33,10 +33,10 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||||
@@ -67,7 +67,7 @@ fun BookmarkListScreen(
|
|||||||
factory = BookmarkPrivateFeedViewModel.Factory(accountViewModel.account),
|
factory = BookmarkPrivateFeedViewModel.Factory(accountViewModel.account),
|
||||||
)
|
)
|
||||||
|
|
||||||
val userState by accountViewModel.account.decryptBookmarks.observeAsState()
|
val userState by accountViewModel.account.decryptBookmarks.collectAsStateWithLifecycle(null)
|
||||||
|
|
||||||
LaunchedEffect(userState) {
|
LaunchedEffect(userState) {
|
||||||
publicFeedViewModel.invalidateData()
|
publicFeedViewModel.invalidateData()
|
||||||
|
|||||||
+14
-18
@@ -60,24 +60,20 @@ class ChatroomFilterAssembler(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun createMessagesFromMeFilter(key: ChatroomQueryState): TypedFilter? =
|
fun createMessagesFromMeFilter(key: ChatroomQueryState): TypedFilter? =
|
||||||
if (key.room != null) {
|
TypedFilter(
|
||||||
TypedFilter(
|
types = setOf(FeedType.PRIVATE_DMS),
|
||||||
types = setOf(FeedType.PRIVATE_DMS),
|
filter =
|
||||||
filter =
|
SincePerRelayFilter(
|
||||||
SincePerRelayFilter(
|
kinds = listOf(PrivateDmEvent.KIND),
|
||||||
kinds = listOf(PrivateDmEvent.KIND),
|
authors = listOf(key.account.userProfile().pubkeyHex),
|
||||||
authors = listOf(key.account.userProfile().pubkeyHex),
|
tags = mapOf("p" to key.room.users.map { it }),
|
||||||
tags = mapOf("p" to key.room.users.map { it }),
|
since =
|
||||||
since =
|
latestEOSEs.users[key.account.userProfile()]
|
||||||
latestEOSEs.users[key.account.userProfile()]
|
?.followList
|
||||||
?.followList
|
?.get(key.room.hashCode().toString())
|
||||||
?.get(key.room.hashCode().toString())
|
?.relayList,
|
||||||
?.relayList,
|
),
|
||||||
),
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearEOSEs(account: Account) {
|
fun clearEOSEs(account: Account) {
|
||||||
latestEOSEs.removeDataFor(account.userProfile())
|
latestEOSEs.removeDataFor(account.userProfile())
|
||||||
|
|||||||
+4
-20
@@ -24,14 +24,12 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
|
||||||
import androidx.lifecycle.map
|
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserRoomSubject
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserShortName
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserShortName
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
@@ -49,14 +47,7 @@ fun RoomNameOnlyDisplay(
|
|||||||
fontWeight: FontWeight = FontWeight.Bold,
|
fontWeight: FontWeight = FontWeight.Bold,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val roomSubject by
|
val roomSubject by observeUserRoomSubject(accountViewModel.userProfile(), room)
|
||||||
accountViewModel
|
|
||||||
.userProfile()
|
|
||||||
.live()
|
|
||||||
.messages
|
|
||||||
.map { it.user.privateChatrooms[room]?.subject }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.observeAsState(accountViewModel.userProfile().privateChatrooms[room]?.subject)
|
|
||||||
|
|
||||||
CrossfadeIfEnabled(targetState = roomSubject, modifier, accountViewModel = accountViewModel) {
|
CrossfadeIfEnabled(targetState = roomSubject, modifier, accountViewModel = accountViewModel) {
|
||||||
if (!it.isNullOrBlank()) {
|
if (!it.isNullOrBlank()) {
|
||||||
@@ -107,16 +98,9 @@ fun RoomNameDisplay(
|
|||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val roomSubject by
|
val roomSubject by observeUserRoomSubject(accountViewModel.userProfile(), room)
|
||||||
accountViewModel
|
|
||||||
.userProfile()
|
|
||||||
.live()
|
|
||||||
.messages
|
|
||||||
.map { it.user.privateChatrooms[room]?.subject }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.observeAsState(accountViewModel.userProfile().privateChatrooms[room]?.subject)
|
|
||||||
|
|
||||||
CrossfadeIfEnabled(targetState = roomSubject, modifier, label = "RoomNameDisplay", accountViewModel = accountViewModel) {
|
CrossfadeIfEnabled(targetState = roomSubject, modifier, accountViewModel = accountViewModel) {
|
||||||
if (!it.isNullOrBlank()) {
|
if (!it.isNullOrBlank()) {
|
||||||
if (room.users.size > 1) {
|
if (room.users.size > 1) {
|
||||||
DisplayRoomSubject(it)
|
DisplayRoomSubject(it)
|
||||||
|
|||||||
-1
@@ -28,7 +28,6 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextFieldDefaults
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|||||||
-2
@@ -21,8 +21,6 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.app.PendingIntentCompat.send
|
|
||||||
import coil3.util.CoilUtils.result
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
||||||
|
|||||||
+23
-28
@@ -32,7 +32,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -41,12 +40,11 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
|
||||||
import androidx.lifecycle.map
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowingChannel
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
@@ -67,7 +65,6 @@ import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.largeProfilePictureModifier
|
import com.vitorpamplona.amethyst.ui.theme.largeProfilePictureModifier
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.events.isTaggedEvent
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LongPublicChatChannelHeader(
|
fun LongPublicChatChannelHeader(
|
||||||
@@ -181,44 +178,42 @@ fun LongChannelActionOptions(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val isMe by
|
|
||||||
remember(accountViewModel) {
|
|
||||||
derivedStateOf { channel.creator == accountViewModel.account.userProfile() }
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenChatButton(channel, accountViewModel, nav)
|
OpenChatButton(channel, accountViewModel, nav)
|
||||||
|
|
||||||
LinkChatButton(channel, accountViewModel, nav)
|
LinkChatButton(channel, accountViewModel, nav)
|
||||||
|
|
||||||
ShareChatButton(channel, accountViewModel, nav)
|
ShareChatButton(channel, accountViewModel, nav)
|
||||||
|
|
||||||
|
EditButtonIfIamCreator(channel, accountViewModel, nav)
|
||||||
|
|
||||||
|
LeaveButtonIfFollowing(channel, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun EditButtonIfIamCreator(
|
||||||
|
channel: PublicChatChannel,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
val isMe by
|
||||||
|
remember(accountViewModel) {
|
||||||
|
derivedStateOf { channel.creator == accountViewModel.account.userProfile() }
|
||||||
|
}
|
||||||
|
|
||||||
if (isMe) {
|
if (isMe) {
|
||||||
EditButton(channel, accountViewModel, nav)
|
EditButton(channel, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
WatchChannelFollows(channel, accountViewModel) { isFollowing ->
|
|
||||||
if (isFollowing) {
|
|
||||||
LeaveChatButton(channel, accountViewModel, nav)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun WatchChannelFollows(
|
fun LeaveButtonIfFollowing(
|
||||||
channel: PublicChatChannel,
|
channel: PublicChatChannel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
content: @Composable (Boolean) -> Unit,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val isFollowing by
|
val isFollowing by observeUserIsFollowingChannel(accountViewModel.userProfile(), channel)
|
||||||
accountViewModel
|
|
||||||
.userProfile()
|
|
||||||
.live()
|
|
||||||
.follows
|
|
||||||
.map { it.user.latestContactList?.isTaggedEvent(channel.idHex) ?: false }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.observeAsState(
|
|
||||||
accountViewModel.userProfile().latestContactList?.isTaggedEvent(channel.idHex) ?: false,
|
|
||||||
)
|
|
||||||
|
|
||||||
content(isFollowing)
|
if (isFollowing) {
|
||||||
|
LeaveChatButton(channel, accountViewModel, nav)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav.popBack
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.TopBarExtensibleWithBackButton
|
import com.vitorpamplona.amethyst.ui.navigation.TopBarExtensibleWithBackButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
+14
-4
@@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.R
|
|||||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowingChannel
|
||||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
@@ -129,9 +130,18 @@ fun ShortChannelActionOptions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WatchChannelFollows(channel, accountViewModel) { isFollowing ->
|
JoinChatButtonIfNotAlreadyJoined(channel, accountViewModel, nav)
|
||||||
if (!isFollowing) {
|
}
|
||||||
JoinChatButton(channel, accountViewModel, nav)
|
|
||||||
}
|
@Composable
|
||||||
|
fun JoinChatButtonIfNotAlreadyJoined(
|
||||||
|
channel: PublicChatChannel,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
val isFollowing by observeUserIsFollowingChannel(accountViewModel.userProfile(), channel)
|
||||||
|
|
||||||
|
if (!isFollowing) {
|
||||||
|
JoinChatButton(channel, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-3
@@ -27,13 +27,10 @@ import androidx.compose.material.icons.filled.EditNote
|
|||||||
import androidx.compose.material3.FilledTonalButton
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
import com.vitorpamplona.amethyst.model.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav.nav
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
-1
@@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.OpenInNew
|
import androidx.compose.material.icons.automirrored.filled.OpenInNew
|
||||||
import androidx.compose.material.icons.filled.OpenInNew
|
|
||||||
import androidx.compose.material3.FilledTonalButton
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|||||||
-2
@@ -28,8 +28,6 @@ import androidx.compose.material.icons.filled.Share
|
|||||||
import androidx.compose.material3.FilledTonalButton
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|||||||
-3
@@ -51,9 +51,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.collections.isNotEmpty
|
|
||||||
import kotlin.collections.map
|
|
||||||
import kotlin.collections.plus
|
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
|
|
||||||
class ChannelMetadataViewModel : ViewModel() {
|
class ChannelMetadataViewModel : ViewModel() {
|
||||||
|
|||||||
+40
-42
@@ -24,15 +24,12 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
|
||||||
import androidx.lifecycle.map
|
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
||||||
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderFilterAssemblerSubscription
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannelInfo
|
||||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -45,47 +42,48 @@ fun ShowVideoStreaming(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
baseChannel.info?.let {
|
baseChannel.info?.let {
|
||||||
SensitivityWarning(
|
val streamingInfoEvent by observeChannelInfo(baseChannel)
|
||||||
event = it,
|
streamingInfoEvent?.let { event ->
|
||||||
accountViewModel = accountViewModel,
|
event.streaming()?.let { url ->
|
||||||
) {
|
val zoomableUrlVideo =
|
||||||
ChannelFinderFilterAssemblerSubscription(baseChannel, accountViewModel.dataSources().channelFinder)
|
remember(streamingInfoEvent) {
|
||||||
val streamingInfoEvent by
|
MediaUrlVideo(
|
||||||
baseChannel.live
|
url = url,
|
||||||
.map {
|
description = event.title() ?: baseChannel.toBestDisplayName(),
|
||||||
(it.channel as? LiveActivitiesChannel)?.info
|
artworkUri = event.image(),
|
||||||
}.distinctUntilChanged()
|
authorName = baseChannel.creatorName(),
|
||||||
.observeAsState(baseChannel.info)
|
uri = baseChannel.toNAddr(),
|
||||||
|
)
|
||||||
streamingInfoEvent?.let { event ->
|
|
||||||
event.streaming()?.let { url ->
|
|
||||||
CrossfadeCheckIfVideoIsOnline(url, accountViewModel) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
modifier = StreamingHeaderModifier,
|
|
||||||
) {
|
|
||||||
val zoomableUrlVideo =
|
|
||||||
remember(streamingInfoEvent) {
|
|
||||||
MediaUrlVideo(
|
|
||||||
url = url,
|
|
||||||
description = baseChannel.toBestDisplayName(),
|
|
||||||
artworkUri = event.image(),
|
|
||||||
authorName = baseChannel.creatorName(),
|
|
||||||
uri = baseChannel.toNAddr(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
ZoomableContentView(
|
|
||||||
content = zoomableUrlVideo,
|
|
||||||
roundedCorner = false,
|
|
||||||
contentScale = ContentScale.FillWidth,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SensitivityWarning(
|
||||||
|
event = event,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
) {
|
||||||
|
RenderStreaming(zoomableUrlVideo, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RenderStreaming(
|
||||||
|
media: MediaUrlVideo,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) {
|
||||||
|
CrossfadeCheckIfVideoIsOnline(media.url, accountViewModel) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
modifier = StreamingHeaderModifier,
|
||||||
|
) {
|
||||||
|
ZoomableContentView(
|
||||||
|
content = media,
|
||||||
|
roundedCorner = false,
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
-1
@@ -48,7 +48,6 @@ import androidx.compose.ui.graphics.graphicsLayer
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav.nav
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.Route.NewGroupDM
|
import com.vitorpamplona.amethyst.ui.navigation.Route.NewGroupDM
|
||||||
|
|||||||
-1
@@ -71,7 +71,6 @@ import com.vitorpamplona.amethyst.ui.note.LoadChannel
|
|||||||
import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContentOrNull
|
import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContentOrNull
|
||||||
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
|
import com.vitorpamplona.amethyst.ui.note.NonClickableUserPictures
|
||||||
import com.vitorpamplona.amethyst.ui.note.ObserveDraftEvent
|
import com.vitorpamplona.amethyst.ui.note.ObserveDraftEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.elements.TimeAgo
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.externalLinkForNote
|
import com.vitorpamplona.amethyst.ui.note.externalLinkForNote
|
||||||
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
import com.vitorpamplona.amethyst.ui.note.timeAgo
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
-1
@@ -30,7 +30,6 @@ import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEventIds
|
|||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
||||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||||
import kotlin.collections.get
|
|
||||||
|
|
||||||
class ChatroomListKnownFeedFilter(
|
class ChatroomListKnownFeedFilter(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
|
|||||||
-1
@@ -28,7 +28,6 @@ import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
|||||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
||||||
import kotlin.collections.get
|
|
||||||
|
|
||||||
class ChatroomListNewFeedFilter(
|
class ChatroomListNewFeedFilter(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
|
|||||||
-1
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.twopane
|
|||||||
|
|
||||||
import androidx.compose.material3.DrawerState
|
import androidx.compose.material3.DrawerState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.EmptyNav.nav
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|||||||
-1
@@ -28,7 +28,6 @@ import com.vitorpamplona.ammolite.relays.TypedFilter
|
|||||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||||
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent
|
||||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||||
import kotlin.collections.ifEmpty
|
|
||||||
|
|
||||||
// This allows multiple screen to be listening to tags, even the same tag
|
// This allows multiple screen to be listening to tags, even the same tag
|
||||||
class CommunityQueryState(
|
class CommunityQueryState(
|
||||||
|
|||||||
-1
@@ -43,7 +43,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.collections.flatten
|
|
||||||
|
|
||||||
// This allows multiple screen to be listening to tags, even the same tag
|
// This allows multiple screen to be listening to tags, even the same tag
|
||||||
class DiscoveryQueryState(
|
class DiscoveryQueryState(
|
||||||
|
|||||||
+2
-12
@@ -24,14 +24,12 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowingGeohash
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
||||||
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
@@ -123,15 +121,7 @@ fun GeoHashActionOptions(
|
|||||||
tag: String,
|
tag: String,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val userState by accountViewModel
|
val isFollowingTag by observeUserIsFollowingGeohash(accountViewModel.userProfile(), tag)
|
||||||
.userProfile()
|
|
||||||
.live()
|
|
||||||
.follows
|
|
||||||
.observeAsState()
|
|
||||||
val isFollowingTag by
|
|
||||||
remember(userState, tag) {
|
|
||||||
derivedStateOf { userState?.user?.isFollowingGeohash(tag) ?: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFollowingTag) {
|
if (isFollowingTag) {
|
||||||
UnfollowButton {
|
UnfollowButton {
|
||||||
|
|||||||
+2
-12
@@ -28,15 +28,13 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowingHashtag
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
||||||
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
@@ -140,15 +138,7 @@ fun HashtagActionOptions(
|
|||||||
tag: String,
|
tag: String,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val userState by accountViewModel
|
val isFollowingTag by observeUserIsFollowingHashtag(accountViewModel.userProfile(), tag)
|
||||||
.userProfile()
|
|
||||||
.live()
|
|
||||||
.follows
|
|
||||||
.observeAsState()
|
|
||||||
val isFollowingTag by
|
|
||||||
remember(userState, tag) {
|
|
||||||
derivedStateOf { userState?.user?.isFollowingHashtag(tag) ?: false }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFollowingTag) {
|
if (isFollowingTag) {
|
||||||
UnfollowButton {
|
UnfollowButton {
|
||||||
|
|||||||
-1
@@ -37,7 +37,6 @@ import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessa
|
|||||||
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
|
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
|
||||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||||
import kotlin.collections.flatten
|
|
||||||
|
|
||||||
// This allows multiple screen to be listening to tags, even the same tag
|
// This allows multiple screen to be listening to tags, even the same tag
|
||||||
class HashtagQueryState(
|
class HashtagQueryState(
|
||||||
|
|||||||
-1
@@ -52,7 +52,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.collections.flatten
|
|
||||||
|
|
||||||
// This allows multiple screen to be listening to tags, even the same tag
|
// This allows multiple screen to be listening to tags, even the same tag
|
||||||
class HomeQueryState(
|
class HomeQueryState(
|
||||||
|
|||||||
+6
-8
@@ -43,7 +43,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -58,11 +57,11 @@ import androidx.compose.ui.layout.onSizeChanged
|
|||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.map
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenUser
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -400,7 +399,10 @@ private fun RenderScreen(
|
|||||||
modifier = tabRowModifier,
|
modifier = tabRowModifier,
|
||||||
divider = { HorizontalDivider(thickness = DividerThickness) },
|
divider = { HorizontalDivider(thickness = DividerThickness) },
|
||||||
) {
|
) {
|
||||||
CreateAndRenderTabs(baseUser, pagerState)
|
CreateAndRenderTabs(
|
||||||
|
baseUser,
|
||||||
|
pagerState,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
@@ -470,11 +472,7 @@ fun UpdateThreadsAndRepliesWhenBlockUnblock(
|
|||||||
repliesViewModel: UserProfileConversationsFeedViewModel,
|
repliesViewModel: UserProfileConversationsFeedViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val isHidden by
|
val isHidden by observeAccountIsHiddenUser(accountViewModel.account, baseUser)
|
||||||
accountViewModel.account.liveHiddenUsers
|
|
||||||
.map {
|
|
||||||
it.hiddenUsers.contains(baseUser.pubkeyHex) || it.spammers.contains(baseUser.pubkeyHex)
|
|
||||||
}.observeAsState(accountViewModel.account.isHidden(baseUser))
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = isHidden) {
|
LaunchedEffect(key1 = isHidden) {
|
||||||
threadsViewModel.invalidateData()
|
threadsViewModel.invalidateData()
|
||||||
|
|||||||
+2
-20
@@ -22,33 +22,15 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.bookmarks
|
|||||||
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserBookmarkCount
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BookmarkTabHeader(baseUser: User) {
|
fun BookmarkTabHeader(baseUser: User) {
|
||||||
val userState by baseUser.live().bookmarks.observeAsState()
|
val userBookmarks by observeUserBookmarkCount(baseUser)
|
||||||
|
|
||||||
var userBookmarks by remember { mutableIntStateOf(0) }
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = userState) {
|
|
||||||
launch(Dispatchers.IO) {
|
|
||||||
val newBookmarks = userState?.user?.latestBookmarkList?.countBookmarks() ?: 0
|
|
||||||
|
|
||||||
if (newBookmarks != userBookmarks) {
|
|
||||||
userBookmarks = newBookmarks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(text = "$userBookmarks ${stringRes(R.string.bookmarks)}")
|
Text(text = "$userBookmarks ${stringRes(R.string.bookmarks)}")
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -52,7 +52,6 @@ import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
|
|||||||
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
|
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
|
||||||
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
|
||||||
import com.vitorpamplona.quartz.nip89AppHandlers.recommendation.AppRecommendationEvent
|
import com.vitorpamplona.quartz.nip89AppHandlers.recommendation.AppRecommendationEvent
|
||||||
import kotlin.collections.flatten
|
|
||||||
|
|
||||||
// This allows multiple screen to be listening to tags, even the same tag
|
// This allows multiple screen to be listening to tags, even the same tag
|
||||||
class UserProfileQueryState(
|
class UserProfileQueryState(
|
||||||
|
|||||||
+8
-20
@@ -22,34 +22,22 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.followers
|
|||||||
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserFollowerCount
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FollowersTabHeader(baseUser: User) {
|
fun FollowersTabHeader(baseUser: User) {
|
||||||
val userState by baseUser.live().followers.observeAsState()
|
val followerCount by observeUserFollowerCount(baseUser)
|
||||||
var followerCount by remember { mutableStateOf("--") }
|
|
||||||
|
|
||||||
val text = stringRes(R.string.followers)
|
val text =
|
||||||
|
if (followerCount > 0) {
|
||||||
LaunchedEffect(key1 = userState) {
|
stringRes(R.string.number_followers, followerCount)
|
||||||
launch(Dispatchers.IO) {
|
} else {
|
||||||
val newFollower = (userState?.user?.transientFollowerCount()?.toString() ?: "--") + " " + text
|
stringRes(R.string.number_followers, "--")
|
||||||
|
|
||||||
if (followerCount != newFollower) {
|
|
||||||
followerCount = newFollower
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Text(text = followerCount)
|
Text(text = text)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -25,9 +25,9 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserFollowers
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView
|
import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel
|
||||||
@@ -52,7 +52,7 @@ private fun WatchFollowerChanges(
|
|||||||
baseUser: User,
|
baseUser: User,
|
||||||
feedViewModel: UserFeedViewModel,
|
feedViewModel: UserFeedViewModel,
|
||||||
) {
|
) {
|
||||||
val userState by baseUser.live().followers.observeAsState()
|
val userState by observeUserFollowers(baseUser)
|
||||||
|
|
||||||
LaunchedEffect(userState) { feedViewModel.invalidateData() }
|
LaunchedEffect(userState) { feedViewModel.invalidateData() }
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-20
@@ -22,34 +22,22 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.follows
|
|||||||
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserFollowCount
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FollowTabHeader(baseUser: User) {
|
fun FollowTabHeader(baseUser: User) {
|
||||||
val userState by baseUser.live().follows.observeAsState()
|
val followCount by observeUserFollowCount(baseUser)
|
||||||
var followCount by remember { mutableStateOf("--") }
|
|
||||||
|
|
||||||
val text = stringRes(R.string.follows)
|
val text =
|
||||||
|
if (followCount > 0) {
|
||||||
LaunchedEffect(key1 = userState) {
|
stringRes(R.string.number_following, followCount)
|
||||||
launch(Dispatchers.IO) {
|
} else {
|
||||||
val newFollow = (userState?.user?.transientFollowCount()?.toString() ?: "--") + " " + text
|
stringRes(R.string.number_following, "--")
|
||||||
|
|
||||||
if (followCount != newFollow) {
|
|
||||||
followCount = newFollow
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Text(text = followCount)
|
Text(text = text)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -25,9 +25,9 @@ import androidx.compose.foundation.layout.fillMaxHeight
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserFollows
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView
|
import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel
|
||||||
@@ -52,7 +52,7 @@ private fun WatchFollowChanges(
|
|||||||
baseUser: User,
|
baseUser: User,
|
||||||
feedViewModel: UserFeedViewModel,
|
feedViewModel: UserFeedViewModel,
|
||||||
) {
|
) {
|
||||||
val userState by baseUser.live().follows.observeAsState()
|
val userState by observeUserFollows(baseUser)
|
||||||
|
|
||||||
LaunchedEffect(userState) { feedViewModel.invalidateData() }
|
LaunchedEffect(userState) { feedViewModel.invalidateData() }
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -33,7 +33,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
||||||
|
|||||||
+2
-10
@@ -22,23 +22,15 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.hashtags
|
|||||||
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserTagFollows
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FollowedTagsTabHeader(baseUser: User) {
|
fun FollowedTagsTabHeader(baseUser: User) {
|
||||||
val userState by baseUser.live().follows.observeAsState()
|
val usertags by observeUserTagFollows(baseUser)
|
||||||
|
|
||||||
val usertags by remember(baseUser) {
|
|
||||||
derivedStateOf {
|
|
||||||
userState?.user?.latestContactList?.countFollowTags() ?: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(text = "$usertags ${stringRes(R.string.followed_tags)}")
|
Text(text = "$usertags ${stringRes(R.string.followed_tags)}")
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-19
@@ -22,11 +22,9 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
|
||||||
import androidx.lifecycle.map
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowing
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.FollowButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.FollowButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.UnfollowButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.UnfollowButton
|
||||||
@@ -36,22 +34,8 @@ fun DisplayFollowUnfollowButton(
|
|||||||
baseUser: User,
|
baseUser: User,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val isLoggedInFollowingUser by
|
val isLoggedInFollowingUser by observeUserIsFollowing(accountViewModel.account.userProfile(), baseUser)
|
||||||
accountViewModel.account
|
val isUserFollowingLoggedIn by observeUserIsFollowing(baseUser, accountViewModel.account.userProfile())
|
||||||
.userProfile()
|
|
||||||
.live()
|
|
||||||
.follows
|
|
||||||
.map { it.user.isFollowing(baseUser) }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.observeAsState(initial = accountViewModel.account.isFollowing(baseUser))
|
|
||||||
|
|
||||||
val isUserFollowingLoggedIn by
|
|
||||||
baseUser
|
|
||||||
.live()
|
|
||||||
.follows
|
|
||||||
.map { it.user.isFollowing(accountViewModel.account.userProfile()) }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.observeAsState(initial = baseUser.isFollowing(accountViewModel.account.userProfile()))
|
|
||||||
|
|
||||||
if (isLoggedInFollowingUser) {
|
if (isLoggedInFollowingUser) {
|
||||||
UnfollowButton {
|
UnfollowButton {
|
||||||
|
|||||||
+12
-3
@@ -53,10 +53,19 @@ fun DrawBanner(
|
|||||||
) {
|
) {
|
||||||
val banner by observeUserBanner(baseUser)
|
val banner by observeUserBanner(baseUser)
|
||||||
|
|
||||||
val clipboardManager = LocalClipboardManager.current
|
DrawBanner(banner, accountViewModel)
|
||||||
var zoomImageDialogOpen by remember { mutableStateOf(false) }
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun DrawBanner(
|
||||||
|
banner: String?,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
) {
|
||||||
|
if (!banner.isNullOrBlank()) {
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
var zoomImageDialogOpen by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if (banner.isNotBlank()) {
|
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = banner,
|
model = banner,
|
||||||
contentDescription = stringRes(id = R.string.profile_image),
|
contentDescription = stringRes(id = R.string.profile_image),
|
||||||
|
|||||||
+7
-7
@@ -25,10 +25,10 @@ import androidx.compose.runtime.derivedStateOf
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenUser
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.ShowUserButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.ShowUserButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.WatchIsHiddenUser
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ProfileActions(
|
fun ProfileActions(
|
||||||
@@ -43,11 +43,11 @@ fun ProfileActions(
|
|||||||
EditButton(nav)
|
EditButton(nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
WatchIsHiddenUser(baseUser, accountViewModel) { isHidden ->
|
val isHidden by observeAccountIsHiddenUser(accountViewModel.account, baseUser)
|
||||||
if (isHidden) {
|
|
||||||
ShowUserButton { accountViewModel.showUser(baseUser.pubkeyHex) }
|
if (isHidden) {
|
||||||
} else {
|
ShowUserButton { accountViewModel.showUser(baseUser.pubkeyHex) }
|
||||||
DisplayFollowUnfollowButton(baseUser, accountViewModel)
|
} else {
|
||||||
}
|
DisplayFollowUnfollowButton(baseUser, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-24
@@ -28,13 +28,16 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.RelayInfo
|
import com.vitorpamplona.amethyst.model.RelayInfo
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.UserState
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
|
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
|
||||||
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
import com.vitorpamplona.ammolite.relays.BundledUpdate
|
||||||
|
import com.vitorpamplona.quartz.nip02FollowList.ReadWrite
|
||||||
import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter
|
import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -51,55 +54,72 @@ class RelayFeedViewModel :
|
|||||||
val feedContent = _feedContent.asStateFlow()
|
val feedContent = _feedContent.asStateFlow()
|
||||||
|
|
||||||
var currentUser: User? = null
|
var currentUser: User? = null
|
||||||
|
var currentJob: Job? = null
|
||||||
|
|
||||||
override val isRefreshing: MutableState<Boolean> = mutableStateOf(false)
|
override val isRefreshing: MutableState<Boolean> = mutableStateOf(false)
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
viewModelScope.launch(Dispatchers.Default) { refreshSuspended() }
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
refreshSuspended()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshSuspended() {
|
fun refreshSuspended() {
|
||||||
try {
|
try {
|
||||||
isRefreshing.value = true
|
isRefreshing.value = true
|
||||||
|
|
||||||
val beingUsed = currentUser?.relaysBeingUsed?.values ?: emptyList()
|
currentUser?.let {
|
||||||
val beingUsedSet = currentUser?.relaysBeingUsed?.keys ?: emptySet()
|
val newList = mergeRelays(it.relaysBeingUsed, it.latestContactList?.relays())
|
||||||
|
_feedContent.update { newList }
|
||||||
val newRelaysFromRecord =
|
}
|
||||||
currentUser?.latestContactList?.relays()?.entries?.mapNotNullTo(HashSet()) {
|
|
||||||
val url = RelayUrlFormatter.normalize(it.key)
|
|
||||||
if (url !in beingUsedSet) {
|
|
||||||
RelayInfo(url, 0, 0)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
?: emptyList()
|
|
||||||
|
|
||||||
val newList = (beingUsed + newRelaysFromRecord).sortedWith(order)
|
|
||||||
|
|
||||||
_feedContent.update { newList }
|
|
||||||
} finally {
|
} finally {
|
||||||
isRefreshing.value = false
|
isRefreshing.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val listener: (UserState) -> Unit = { invalidateData() }
|
fun mergeRelays(
|
||||||
|
relaysBeingUsed: Map<String, RelayInfo>,
|
||||||
|
relays: Map<String, ReadWrite>?,
|
||||||
|
): List<RelayInfo> {
|
||||||
|
val userRelaysBeingUsed = relaysBeingUsed.map { it.value }
|
||||||
|
|
||||||
|
val currentUserRelays =
|
||||||
|
relays?.mapNotNull {
|
||||||
|
val url = RelayUrlFormatter.normalize(it.key)
|
||||||
|
if (url !in relaysBeingUsed) {
|
||||||
|
RelayInfo(url, 0, 0)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} ?: emptyList()
|
||||||
|
|
||||||
|
return (userRelaysBeingUsed + currentUserRelays).sortedWith(order)
|
||||||
|
}
|
||||||
|
|
||||||
fun subscribeTo(user: User) {
|
fun subscribeTo(user: User) {
|
||||||
if (currentUser != user) {
|
if (currentUser != user) {
|
||||||
currentUser = user
|
currentUser = user
|
||||||
user.live().relays.observeForever(listener)
|
|
||||||
user.live().relayInfo.observeForever(listener)
|
currentJob?.cancel()
|
||||||
|
currentJob =
|
||||||
|
viewModelScope.launch {
|
||||||
|
combine(currentUser!!.flow().relays.stateFlow, currentUser!!.flow().relayInfo.stateFlow) { relays, relayInfo ->
|
||||||
|
mergeRelays(relays.user.relaysBeingUsed, relayInfo.user.latestContactList?.relays())
|
||||||
|
}.debounce(1000)
|
||||||
|
.collect { newList ->
|
||||||
|
_feedContent.update { newList }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
invalidateData()
|
invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unsubscribeTo(user: User) {
|
fun unsubscribeTo(user: User) {
|
||||||
if (currentUser == user) {
|
if (currentUser == user) {
|
||||||
user.live().relays.removeObserver(listener)
|
|
||||||
user.live().relayInfo.removeObserver(listener)
|
|
||||||
currentUser = null
|
currentUser = null
|
||||||
|
currentJob?.cancel()
|
||||||
|
invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +136,7 @@ class RelayFeedViewModel :
|
|||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
Log.d("Init", "OnCleared: ${this.javaClass.simpleName}")
|
Log.d("Init", "OnCleared: ${this.javaClass.simpleName}")
|
||||||
bundler.cancel()
|
bundler.cancel()
|
||||||
|
currentJob?.cancel()
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-15
@@ -23,26 +23,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.relays
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserRelaysUsing
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RelaysTabHeader(baseUser: User) {
|
fun RelaysTabHeader(baseUser: User) {
|
||||||
val userState by baseUser.live().relays.observeAsState()
|
val userState by observeUserRelaysUsing(baseUser)
|
||||||
val userRelaysBeingUsed = remember(userState) { userState?.user?.relaysBeingUsed?.size ?: "--" }
|
|
||||||
|
|
||||||
val userStateRelayInfo by baseUser.live().relayInfo.observeAsState()
|
Text(text = "${sizeAsString(userState.userRelayList.size)} / ${sizeAsString(userState.relays.size)} ${stringRes(R.string.relays)}")
|
||||||
val userRelays =
|
|
||||||
remember(userStateRelayInfo) {
|
|
||||||
userStateRelayInfo
|
|
||||||
?.user
|
|
||||||
?.latestContactList
|
|
||||||
?.relays()
|
|
||||||
?.size ?: "--"
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(text = "$userRelaysBeingUsed / $userRelays ${stringRes(R.string.relays)}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun sizeAsString(count: Int) = if (count > 0) count.toString() else "--"
|
||||||
|
|||||||
+6
-20
@@ -22,33 +22,19 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.reports
|
|||||||
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.reports.dal.UserProfileReportsFeedFilter
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserReportCount
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReportsTabHeader(baseUser: User) {
|
fun ReportsTabHeader(baseUser: User) {
|
||||||
val userState by baseUser.live().reports.observeAsState()
|
val reportCount by observeUserReportCount(baseUser)
|
||||||
var userReports by remember { mutableIntStateOf(0) }
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = userState) {
|
if (reportCount > 0) {
|
||||||
launch(Dispatchers.IO) {
|
Text(text = stringRes(R.string.number_reports, reportCount))
|
||||||
val newSize = UserProfileReportsFeedFilter(baseUser).feed().size
|
} else {
|
||||||
|
Text(text = stringRes(R.string.reports))
|
||||||
if (newSize != userReports) {
|
|
||||||
userReports = newSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(text = "$userReports ${stringRes(R.string.reports)}")
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.reports
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserReports
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.reports.dal.UserProfileReportFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.reports.dal.UserProfileReportFeedViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -32,6 +32,6 @@ fun WatchReportsAndUpdateFeed(
|
|||||||
baseUser: User,
|
baseUser: User,
|
||||||
feedViewModel: UserProfileReportFeedViewModel,
|
feedViewModel: UserProfileReportFeedViewModel,
|
||||||
) {
|
) {
|
||||||
val userState by baseUser.live().reports.observeAsState()
|
val userState by observeUserReports(baseUser)
|
||||||
LaunchedEffect(userState) { feedViewModel.invalidateData() }
|
LaunchedEffect(userState) { feedViewModel.invalidateData() }
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -25,7 +25,6 @@ import androidx.compose.runtime.Stable
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache.notes
|
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists
|
||||||
|
|||||||
-43
@@ -1,43 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2024 Vitor Pamplona
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
|
||||||
* the Software without restriction, including without limitation the rights to use,
|
|
||||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.lifecycle.map
|
|
||||||
import com.vitorpamplona.amethyst.model.User
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun WatchIsHiddenUser(
|
|
||||||
baseUser: User,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
content: @Composable (Boolean) -> Unit,
|
|
||||||
) {
|
|
||||||
val isHidden by
|
|
||||||
accountViewModel.account.liveHiddenUsers
|
|
||||||
.map {
|
|
||||||
it.hiddenUsers.contains(baseUser.pubkeyHex) || it.spammers.contains(baseUser.pubkeyHex)
|
|
||||||
}.observeAsState(accountViewModel.account.isHidden(baseUser))
|
|
||||||
|
|
||||||
content(isHidden)
|
|
||||||
}
|
|
||||||
+2
-2
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserZaps
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.dal.UserProfileZapsFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.dal.UserProfileZapsFeedViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -32,7 +32,7 @@ fun WatchZapsAndUpdateFeed(
|
|||||||
baseUser: User,
|
baseUser: User,
|
||||||
feedViewModel: UserProfileZapsFeedViewModel,
|
feedViewModel: UserProfileZapsFeedViewModel,
|
||||||
) {
|
) {
|
||||||
val userState by baseUser.live().zaps.observeAsState()
|
val userState by observeUserZaps(baseUser)
|
||||||
|
|
||||||
LaunchedEffect(userState) { feedViewModel.invalidateData() }
|
LaunchedEffect(userState) { feedViewModel.invalidateData() }
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-19
@@ -22,33 +22,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps
|
|||||||
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserZapAmount
|
||||||
import com.vitorpamplona.amethyst.ui.note.showAmountInteger
|
import com.vitorpamplona.amethyst.ui.note.showAmountInteger
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.math.BigDecimal
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ZapTabHeader(baseUser: User) {
|
fun ZapTabHeader(baseUser: User) {
|
||||||
val userState by baseUser.live().zaps.observeAsState()
|
val zapAmount by observeUserZapAmount(baseUser)
|
||||||
var zapAmount by remember { mutableStateOf<BigDecimal?>(null) }
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = userState) {
|
|
||||||
launch(Dispatchers.Default) {
|
|
||||||
val tempAmount = baseUser.zappedAmount()
|
|
||||||
if (zapAmount != tempAmount) {
|
|
||||||
zapAmount = tempAmount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(text = "${showAmountInteger(zapAmount)} ${stringRes(id = R.string.zaps)}")
|
Text(text = "${showAmountInteger(zapAmount)} ${stringRes(id = R.string.zaps)}")
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -42,7 +42,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.collections.plus
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class Kind3RelayListViewModel : ViewModel() {
|
class Kind3RelayListViewModel : ViewModel() {
|
||||||
|
|||||||
+2
-8
@@ -46,7 +46,6 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
@@ -63,10 +62,9 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
|
||||||
import androidx.lifecycle.map
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.observeAccountIsHiddenWord
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||||
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
@@ -323,11 +321,7 @@ fun MutedWordActionOptions(
|
|||||||
word: String,
|
word: String,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val isMutedWord by
|
val isMutedWord by observeAccountIsHiddenWord(accountViewModel.account, word)
|
||||||
accountViewModel.account.liveHiddenUsers
|
|
||||||
.map { word in it.hiddenWords }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
.observeAsState()
|
|
||||||
|
|
||||||
if (isMutedWord == true) {
|
if (isMutedWord == true) {
|
||||||
ShowWordButton {
|
ShowWordButton {
|
||||||
|
|||||||
-2
@@ -28,8 +28,6 @@ import com.vitorpamplona.ammolite.relays.NostrClient
|
|||||||
import com.vitorpamplona.ammolite.relays.TypedFilter
|
import com.vitorpamplona.ammolite.relays.TypedFilter
|
||||||
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
import com.vitorpamplona.ammolite.relays.filters.SincePerRelayFilter
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import kotlin.collections.flatten
|
|
||||||
import kotlin.collections.ifEmpty
|
|
||||||
|
|
||||||
// This allows multiple screen to be listening to tags, even the same tag
|
// This allows multiple screen to be listening to tags, even the same tag
|
||||||
class ThreadQueryState(
|
class ThreadQueryState(
|
||||||
|
|||||||
-1
@@ -39,7 +39,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.collections.flatten
|
|
||||||
|
|
||||||
val SUPPORTED_VIDEO_FEED_MIME_TYPES = listOf("image/jpeg", "image/gif", "image/png", "image/webp", "video/mp4", "video/mpeg", "video/webm", "audio/aac", "audio/mpeg", "audio/webm", "audio/wav", "image/avif")
|
val SUPPORTED_VIDEO_FEED_MIME_TYPES = listOf("image/jpeg", "image/gif", "image/png", "image/webp", "video/mp4", "video/mpeg", "video/webm", "audio/aac", "audio/mpeg", "audio/webm", "audio/wav", "image/avif")
|
||||||
val SUPPORTED_VIDEO_FEED_MIME_TYPES_SET = SUPPORTED_VIDEO_FEED_MIME_TYPES.toSet()
|
val SUPPORTED_VIDEO_FEED_MIME_TYPES_SET = SUPPORTED_VIDEO_FEED_MIME_TYPES.toSet()
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import kotlinx.coroutines.flow.SharingStarted
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import org.torproject.jni.TorService
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There should be only one instance of the Tor binding per app.
|
* There should be only one instance of the Tor binding per app.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user