Loading Users and Notes in Launched Effects only because of @Synchronized in the getOrCreate methods in LocalCache

This commit is contained in:
Vitor Pamplona
2023-05-07 12:51:12 -04:00
parent 1c0fcc4ab8
commit f44549de41
22 changed files with 322 additions and 123 deletions
@@ -820,7 +820,7 @@ class Account(
return if (event is PrivateDmEvent && loggedIn.privKey != null) {
var pubkeyToUse = event.pubKey
val recepientPK = event.recipientPubKey()
val recepientPK = event.verifiedRecipientPubKey()
if (note.author == userProfile() && recepientPK != null) {
pubkeyToUse = recepientPK
@@ -359,7 +359,7 @@ object LocalCache {
// Already processed this event.
if (note.event != null) return
val recipient = event.recipientPubKey()?.let { getOrCreateUser(it) }
val recipient = event.verifiedRecipientPubKey()?.let { getOrCreateUser(it) }
// Log.d("PM", "${author.toBestDisplayName()} to ${recipient?.toBestDisplayName()}")
@@ -407,7 +407,7 @@ object LocalCache {
if (deleteNote.event is PrivateDmEvent) {
val author = deleteNote.author
val recipient = (deleteNote.event as? PrivateDmEvent)?.recipientPubKey()?.let { checkGetOrCreateUser(it) }
val recipient = (deleteNote.event as? PrivateDmEvent)?.verifiedRecipientPubKey()?.let { checkGetOrCreateUser(it) }
if (recipient != null && author != null) {
author.removeMessage(recipient, deleteNote)
@@ -6,7 +6,7 @@ import kotlin.time.measureTimedValue
class ThreadAssembler {
fun searchRoot(note: Note, testedNotes: MutableSet<Note> = mutableSetOf()): Note? {
private fun searchRoot(note: Note, testedNotes: MutableSet<Note> = mutableSetOf()): Note? {
if (note.replyTo == null || note.replyTo?.isEmpty() == true) return note
testedNotes.add(note)
@@ -181,7 +181,7 @@ class User(val pubkeyHex: String) {
}
@Synchronized
fun getOrCreatePrivateChatroom(user: User): Chatroom {
private fun getOrCreatePrivateChatroom(user: User): Chatroom {
return privateChatrooms[user] ?: run {
val privateChatroom = Chatroom(setOf<Note>())
privateChatrooms = privateChatrooms + Pair(user, privateChatroom)
@@ -1,6 +1,5 @@
package com.vitorpamplona.amethyst.service
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.*
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
@@ -10,13 +9,8 @@ import com.vitorpamplona.amethyst.service.relays.TypedFilter
object NostrUserProfileDataSource : NostrDataSource("UserProfileFeed") {
var user: User? = null
fun loadUserProfile(userId: String?) {
if (userId != null) {
user = LocalCache.getOrCreateUser(userId)
} else {
user = null
}
fun loadUserProfile(user: User?) {
this.user = user
resetFilters()
}
@@ -21,7 +21,11 @@ class PrivateDmEvent(
* nip-04 EncryptedDmEvent but may omit the recipient, too. This value can be queried and used
* for initial messages.
*/
fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.run { Hex.decode(this[1]).toHexKey() } // makes sure its a valid one
fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }
fun recipientPubKeyBytes() = recipientPubKey()?.runCatching { Hex.decode(this[1]) }?.getOrNull()
fun verifiedRecipientPubKey() = recipientPubKey()?.runCatching { Hex.decode(this[1]).toHexKey() }?.getOrNull() // makes sure its a valid one
/**
* To be fully compatible with nip-04, we read e-tags that are in violation to nip-18.
@@ -5,15 +5,23 @@ import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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 androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
import com.vitorpamplona.amethyst.service.nip19.Nip19
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun ClickableRoute(
@@ -21,56 +29,190 @@ fun ClickableRoute(
navController: NavController
) {
if (nip19.type == Nip19.Type.USER) {
val userBase = LocalCache.getOrCreateUser(nip19.hex)
val userState by userBase.live().metadata.observeAsState()
val user = userState?.user ?: return
CreateClickableText(user.toBestDisplayName(), nip19.additionalChars, "User/${nip19.hex}", navController)
DisplayUser(nip19, navController)
} else if (nip19.type == Nip19.Type.ADDRESS) {
val noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex)
DisplayAddress(nip19, navController)
} else if (nip19.type == Nip19.Type.NOTE) {
DisplayNote(nip19, navController)
} else if (nip19.type == Nip19.Type.EVENT) {
DisplayEvent(nip19, navController)
} else {
Text(
"@${nip19.hex}${nip19.additionalChars} "
)
}
}
if (noteBase == null) {
Text(
"@${nip19.hex}${nip19.additionalChars} "
@Composable
private fun DisplayEvent(
nip19: Nip19.Return,
navController: NavController
) {
var noteBase by remember { mutableStateOf<Note?>(null) }
LaunchedEffect(key1 = nip19.hex) {
withContext(Dispatchers.IO) {
noteBase = LocalCache.checkGetOrCreateNote(nip19.hex)
}
}
noteBase?.let {
val noteState by it.live().metadata.observeAsState()
val note = noteState?.note ?: return
val channel = note.channel()
if (note.event is ChannelCreateEvent) {
CreateClickableText(
note.idDisplayNote(),
nip19.additionalChars,
"Channel/${nip19.hex}",
navController
)
} else if (note.event is PrivateDmEvent) {
CreateClickableText(
note.idDisplayNote(),
nip19.additionalChars,
"Room/${note.author?.pubkeyHex}",
navController
)
} else if (channel != null) {
CreateClickableText(
channel.toBestDisplayName(),
nip19.additionalChars,
"Channel/${note.channel()?.idHex}",
navController
)
} else {
val noteState by noteBase.live().metadata.observeAsState()
val note = noteState?.note ?: return
CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Note/${nip19.hex}", navController)
CreateClickableText(
note.idDisplayNote(),
nip19.additionalChars,
"Event/${nip19.hex}",
navController
)
}
} else if (nip19.type == Nip19.Type.NOTE) {
val noteBase = LocalCache.getOrCreateNote(nip19.hex)
val noteState by noteBase.live().metadata.observeAsState()
}
if (noteBase == null) {
Text(
"@${nip19.hex}${nip19.additionalChars} "
)
}
}
@Composable
private fun DisplayNote(
nip19: Nip19.Return,
navController: NavController
) {
var noteBase by remember { mutableStateOf<Note?>(null) }
LaunchedEffect(key1 = nip19.hex) {
withContext(Dispatchers.IO) {
noteBase = LocalCache.checkGetOrCreateNote(nip19.hex)
}
}
noteBase?.let {
val noteState by it.live().metadata.observeAsState()
val note = noteState?.note ?: return
val channel = note.channel()
if (note.event is ChannelCreateEvent) {
CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Channel/${nip19.hex}", navController)
CreateClickableText(
note.idDisplayNote(),
nip19.additionalChars,
"Channel/${nip19.hex}",
navController
)
} else if (note.event is PrivateDmEvent) {
CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Room/${note.author?.pubkeyHex}", navController)
CreateClickableText(
note.idDisplayNote(),
nip19.additionalChars,
"Room/${note.author?.pubkeyHex}",
navController
)
} else if (channel != null) {
CreateClickableText(channel.toBestDisplayName(), nip19.additionalChars, "Channel/${note.channel()?.idHex}", navController)
CreateClickableText(
channel.toBestDisplayName(),
nip19.additionalChars,
"Channel/${note.channel()?.idHex}",
navController
)
} else {
CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Note/${nip19.hex}", navController)
CreateClickableText(
note.idDisplayNote(),
nip19.additionalChars,
"Note/${nip19.hex}",
navController
)
}
} else if (nip19.type == Nip19.Type.EVENT) {
val noteBase = LocalCache.getOrCreateNote(nip19.hex)
val noteState by noteBase.live().metadata.observeAsState()
val note = noteState?.note ?: return
val channel = note.channel()
}
if (note.event is ChannelCreateEvent) {
CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Channel/${nip19.hex}", navController)
} else if (note.event is PrivateDmEvent) {
CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Room/${note.author?.pubkeyHex}", navController)
} else if (channel != null) {
CreateClickableText(channel.toBestDisplayName(), nip19.additionalChars, "Channel/${note.channel()?.idHex}", navController)
} else {
CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Event/${nip19.hex}", navController)
if (noteBase == null) {
Text(
"@${nip19.hex}${nip19.additionalChars} "
)
}
}
@Composable
private fun DisplayAddress(
nip19: Nip19.Return,
navController: NavController
) {
var noteBase by remember { mutableStateOf<Note?>(null) }
LaunchedEffect(key1 = nip19.hex) {
withContext(Dispatchers.IO) {
noteBase = LocalCache.checkGetOrCreateAddressableNote(nip19.hex)
}
} else {
}
noteBase?.let {
val noteState by it.live().metadata.observeAsState()
val note = noteState?.note ?: return
CreateClickableText(
note.idDisplayNote(),
nip19.additionalChars,
"Note/${nip19.hex}",
navController
)
}
if (noteBase == null) {
Text(
"@${nip19.hex}${nip19.additionalChars} "
)
}
}
@Composable
private fun DisplayUser(
nip19: Nip19.Return,
navController: NavController
) {
var userBase by remember { mutableStateOf<User?>(null) }
LaunchedEffect(key1 = nip19.hex) {
withContext(Dispatchers.IO) {
userBase = LocalCache.checkGetOrCreateUser(nip19.hex)
}
}
userBase?.let {
val userState by it.live().metadata.observeAsState()
val user = userState?.user ?: return
CreateClickableText(
user.toBestDisplayName(),
nip19.additionalChars,
"User/${nip19.hex}",
navController
)
}
if (userBase == null) {
Text(
"@${nip19.hex}${nip19.additionalChars} "
)
@@ -60,8 +60,10 @@ import java.io.File
public var muted = mutableStateOf(true)
@Composable
fun VideoView(localFile: File, description: String? = null, onDialog: ((Boolean) -> Unit)? = null) {
VideoView(localFile.toUri(), description, onDialog)
fun VideoView(localFile: File?, description: String? = null, onDialog: ((Boolean) -> Unit)? = null) {
if (localFile != null) {
VideoView(localFile.toUri(), description, onDialog)
}
}
@Composable
@@ -111,7 +111,7 @@ class ZoomableUrlVideo(
) : ZoomableUrlContent(url, description, hash, dim, uri)
abstract class ZoomablePreloadedContent(
val localFile: File,
val localFile: File?,
description: String? = null,
val mimeType: String? = null,
val isVerified: Boolean? = null,
@@ -120,7 +120,7 @@ abstract class ZoomablePreloadedContent(
) : ZoomableContent(description, dim)
class ZoomableLocalImage(
localFile: File,
localFile: File?,
mimeType: String? = null,
description: String? = null,
val blurhash: String? = null,
@@ -130,7 +130,7 @@ class ZoomableLocalImage(
) : ZoomablePreloadedContent(localFile, description, mimeType, isVerified, dim, uri)
class ZoomableLocalVideo(
localFile: File,
localFile: File?,
mimeType: String? = null,
description: String? = null,
dim: String? = null,
@@ -219,7 +219,7 @@ private fun LocalImageView(
}
val contentScale = if (maxHeight.isFinite) ContentScale.Fit else ContentScale.FillWidth
if (content.localFile.exists()) {
if (content.localFile != null && content.localFile.exists()) {
AsyncImage(
model = content.localFile,
contentDescription = content.description,
@@ -246,7 +246,7 @@ private fun LocalImageView(
}
}
if (imageState is AsyncImagePainter.State.Error || !content.localFile.exists()) {
if (imageState is AsyncImagePainter.State.Error || content.localFile == null || !content.localFile.exists()) {
BlankNote()
}
}
@@ -9,9 +9,9 @@ object UserProfileBookmarksFeedFilter : FeedFilter<Note>() {
lateinit var account: Account
var user: User? = null
fun loadUserProfile(accountLoggedIn: Account, userId: String) {
fun loadUserProfile(accountLoggedIn: Account, user: User?) {
account = accountLoggedIn
user = LocalCache.users[userId]
this.user = user
}
override fun feed(): List<Note> {
@@ -1,7 +1,6 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
@@ -9,9 +8,9 @@ object UserProfileConversationsFeedFilter : FeedFilter<Note>() {
var account: Account? = null
var user: User? = null
fun loadUserProfile(accountLoggedIn: Account, userId: String) {
fun loadUserProfile(accountLoggedIn: Account, user: User?) {
account = accountLoggedIn
user = LocalCache.checkGetOrCreateUser(userId)
this.user = user
}
override fun feed(): List<Note> {
@@ -8,9 +8,9 @@ object UserProfileFollowersFeedFilter : FeedFilter<User>() {
lateinit var account: Account
var user: User? = null
fun loadUserProfile(accountLoggedIn: Account, userId: String) {
fun loadUserProfile(accountLoggedIn: Account, user: User?) {
account = accountLoggedIn
user = LocalCache.users[userId]
this.user = user
}
override fun feed(): List<User> {
@@ -8,9 +8,9 @@ object UserProfileFollowsFeedFilter : FeedFilter<User>() {
lateinit var account: Account
var user: User? = null
fun loadUserProfile(accountLoggedIn: Account, userId: String) {
fun loadUserProfile(accountLoggedIn: Account, user: User?) {
account = accountLoggedIn
user = LocalCache.users[userId]
this.user = user
}
override fun feed(): List<User> {
@@ -9,9 +9,9 @@ object UserProfileNewThreadFeedFilter : FeedFilter<Note>() {
var account: Account? = null
var user: User? = null
fun loadUserProfile(accountLoggedIn: Account, userId: String) {
fun loadUserProfile(accountLoggedIn: Account, user: User) {
account = accountLoggedIn
user = LocalCache.checkGetOrCreateUser(userId)
this.user = user
}
override fun feed(): List<Note> {
@@ -1,14 +1,13 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
object UserProfileReportsFeedFilter : FeedFilter<Note>() {
var user: User? = null
fun loadUserProfile(userId: String) {
user = LocalCache.checkGetOrCreateUser(userId)
fun loadUserProfile(user: User?) {
this.user = user
}
override fun feed(): List<Note> {
@@ -1,6 +1,5 @@
package com.vitorpamplona.amethyst.ui.dal
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.model.zaps.UserZaps
@@ -8,8 +7,8 @@ import com.vitorpamplona.amethyst.service.model.zaps.UserZaps
object UserProfileZapsFeedFilter : FeedFilter<Pair<Note, Note>>() {
var user: User? = null
fun loadUserProfile(userId: String) {
user = LocalCache.checkGetOrCreateUser(userId)
fun loadUserProfile(user: User?) {
this.user = user
}
override fun feed(): List<Pair<Note, Note>> {
@@ -134,7 +134,7 @@ fun ChatroomCompose(
} else {
val replyAuthorBase =
(note.event as? PrivateDmEvent)
?.recipientPubKey()
?.verifiedRecipientPubKey()
?.let { LocalCache.getOrCreateUser(it) }
var userToComposeOn = note.author!!
@@ -89,7 +89,6 @@ import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
import com.vitorpamplona.amethyst.service.model.EventInterface
import com.vitorpamplona.amethyst.service.model.FileHeaderEvent
import com.vitorpamplona.amethyst.service.model.FileStorageEvent
import com.vitorpamplona.amethyst.service.model.FileStorageHeaderEvent
import com.vitorpamplona.amethyst.service.model.HighlightEvent
import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
@@ -122,6 +121,7 @@ import com.vitorpamplona.amethyst.ui.theme.Following
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import nostr.postr.toNpub
import java.io.File
import java.math.BigDecimal
import java.net.URL
@@ -387,7 +387,7 @@ fun routeFor(note: Note, loggedIn: User): String? {
} else if (noteEvent is PrivateDmEvent) {
val replyAuthorBase =
(note.event as? PrivateDmEvent)
?.recipientPubKey()
?.verifiedRecipientPubKey()
?.let { LocalCache.getOrCreateUser(it) }
var userToComposeOn = note.author!!
@@ -573,13 +573,13 @@ private fun RenderPrivateMessage(
}
}
} else {
val recipient = noteEvent.recipientPubKey()?.let { LocalCache.checkGetOrCreateUser(it) }
val recipient = noteEvent.recipientPubKeyBytes()?.toNpub() ?: "Someone"
TranslatableRichTextViewer(
stringResource(
id = R.string.private_conversation_notification,
"@${note.author?.pubkeyNpub()}",
"@${recipient?.pubkeyNpub()}"
"@$recipient"
),
canPreview = !makeItShort,
Modifier.fillMaxWidth(),
@@ -797,13 +797,8 @@ private fun ReplyRow(
Spacer(modifier = Modifier.height(5.dp))
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
val sortedMentions = noteEvent.mentions()
.mapNotNull { LocalCache.checkGetOrCreateUser(it) }
.toSet()
.sortedBy { account.isFollowing(it) }
note.channel()?.let {
ReplyInformationChannel(note.replyTo, sortedMentions, it, navController)
ReplyInformationChannel(note.replyTo, noteEvent.mentions(), it, account, navController)
}
Spacer(modifier = Modifier.height(5.dp))
@@ -1016,11 +1011,19 @@ fun DisplayHighlight(
navController
)
var userBase by remember { mutableStateOf<User?>(null) }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
if (authorHex != null) {
userBase = LocalCache.checkGetOrCreateUser(authorHex)
}
}
}
FlowRow() {
authorHex?.let { authorHex ->
val userBase = LocalCache.checkGetOrCreateUser(authorHex)
if (userBase != null) {
userBase?.let { userBase ->
val userState by userBase.live().metadata.observeAsState()
val user = userState?.user
@@ -1307,20 +1310,23 @@ fun FileStorageHeaderDisplay(baseNote: Note) {
val appContext = LocalContext.current.applicationContext
val eventHeader = (baseNote.event as? FileStorageHeaderEvent) ?: return
val fileNote = eventHeader.dataEventId()?.let { LocalCache.checkGetOrCreateNote(it) } ?: return
var fileNote by remember { mutableStateOf<Note?>(null) }
val noteState by fileNote.live().metadata.observeAsState()
val note = noteState?.note
LaunchedEffect(key1 = eventHeader.id) {
withContext(Dispatchers.IO) {
fileNote = eventHeader.dataEventId()?.let { LocalCache.checkGetOrCreateNote(it) }
}
}
val eventBytes = (note?.event as? FileStorageEvent)
val noteState = fileNote?.live()?.metadata?.observeAsState()
val note = noteState?.value?.note
var content by remember { mutableStateOf<ZoomableContent?>(null) }
LaunchedEffect(key1 = eventHeader.id, key2 = noteState) {
LaunchedEffect(key1 = eventHeader.id, key2 = noteState, key3 = note?.event) {
withContext(Dispatchers.IO) {
val uri = "nostr:" + baseNote.toNEvent()
val localDir = File(File(appContext.externalCacheDir, "NIP95"), fileNote.idHex)
val bytes = eventBytes?.decode()
val localDir = note?.idHex?.let { File(File(appContext.externalCacheDir, "NIP95"), it) }
val blurHash = eventHeader.blurhash()
val dimensions = eventHeader.dimensions()
val description = eventHeader.content
@@ -1337,18 +1343,14 @@ fun FileStorageHeaderDisplay(baseNote: Note) {
uri = uri
)
} else {
if (bytes != null) {
ZoomableLocalVideo(
localFile = localDir,
mimeType = mimeType,
description = description,
dim = dimensions,
isVerified = true,
uri = uri
)
} else {
null
}
ZoomableLocalVideo(
localFile = localDir,
mimeType = mimeType,
description = description,
dim = dimensions,
isVerified = true,
uri = uri
)
}
}
}
@@ -5,6 +5,7 @@ import androidx.compose.material.LocalTextStyle
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
@@ -17,13 +18,23 @@ import androidx.navigation.NavController
import com.google.accompanist.flowlayout.FlowRow
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@Composable
fun ReplyInformation(replyTo: List<Note>?, mentions: List<String>, account: Account, navController: NavController) {
val dupMentions = mentions.mapNotNull { LocalCache.checkGetOrCreateUser(it) }
var dupMentions by remember { mutableStateOf<List<User>?>(null) }
ReplyInformation(replyTo, dupMentions, account) {
navController.navigate("User/${it.pubkeyHex}")
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
dupMentions = mentions.mapNotNull { LocalCache.checkGetOrCreateUser(it) }
}
}
if (dupMentions != null) {
ReplyInformation(replyTo, dupMentions, account) {
navController.navigate("User/${it.pubkeyHex}")
}
}
}
@@ -102,6 +113,34 @@ fun ReplyInformation(replyTo: List<Note>?, dupMentions: List<User>?, account: Ac
}
}
@Composable
fun ReplyInformationChannel(replyTo: List<Note>?, mentions: List<String>, channel: Channel, account: Account, navController: NavController) {
var sortedMentions by remember { mutableStateOf<List<User>?>(null) }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
sortedMentions = mentions
.mapNotNull { LocalCache.checkGetOrCreateUser(it) }
.toSet()
.sortedBy { account.isFollowing(it) }
}
}
if (sortedMentions != null) {
ReplyInformationChannel(
replyTo,
sortedMentions,
channel,
onUserTagClick = {
navController.navigate("User/${it.pubkeyHex}")
},
onChannelTagClick = {
navController.navigate("Channel/${it.idHex}")
}
)
}
}
@Composable
fun ReplyInformationChannel(replyTo: List<Note>?, mentions: List<User>?, channel: Channel, navController: NavController) {
ReplyInformationChannel(
@@ -175,7 +175,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
}
}
fun refreshFromOldState(newItems: Set<Note>) {
private fun refreshFromOldState(newItems: Set<Note>) {
val oldNotesState = _feedContent.value
val thisAccount = (localFilter as? NotificationFeedFilter)?.account
@@ -104,7 +104,7 @@ private fun FeedLoaded(
} else {
val replyAuthorBase =
(note.event as? PrivateDmEvent)
?.recipientPubKey()
?.verifiedRecipientPubKey()
?.let { LocalCache.getOrCreateUser(it) }
var userToComposeOn = note.author!!
@@ -100,23 +100,42 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.math.BigDecimal
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navController: NavController) {
if (userId == null) return
var userBase by remember { mutableStateOf<User?>(null) }
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
userBase = LocalCache.checkGetOrCreateUser(userId)
}
}
userBase?.let {
ProfileScreen(
user = it,
accountViewModel = accountViewModel,
navController = navController
)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ProfileScreen(user: User, accountViewModel: AccountViewModel, navController: NavController) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
if (userId == null) return
UserProfileNewThreadFeedFilter.loadUserProfile(account, user)
UserProfileConversationsFeedFilter.loadUserProfile(account, user)
UserProfileFollowersFeedFilter.loadUserProfile(account, user)
UserProfileFollowsFeedFilter.loadUserProfile(account, user)
UserProfileZapsFeedFilter.loadUserProfile(user)
UserProfileReportsFeedFilter.loadUserProfile(user)
UserProfileBookmarksFeedFilter.loadUserProfile(account, user)
UserProfileNewThreadFeedFilter.loadUserProfile(account, userId)
UserProfileConversationsFeedFilter.loadUserProfile(account, userId)
UserProfileFollowersFeedFilter.loadUserProfile(account, userId)
UserProfileFollowsFeedFilter.loadUserProfile(account, userId)
UserProfileZapsFeedFilter.loadUserProfile(userId)
UserProfileReportsFeedFilter.loadUserProfile(userId)
UserProfileBookmarksFeedFilter.loadUserProfile(account, userId)
NostrUserProfileDataSource.loadUserProfile(userId)
NostrUserProfileDataSource.loadUserProfile(user)
val lifeCycleOwner = LocalLifecycleOwner.current
@@ -124,7 +143,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Profidle Start")
NostrUserProfileDataSource.loadUserProfile(userId)
NostrUserProfileDataSource.loadUserProfile(user)
NostrUserProfileDataSource.start()
}
if (event == Lifecycle.Event.ON_PAUSE) {