diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 978bbc38a..0437f7bb3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -369,7 +369,7 @@ class Account( } } - fun isHidden(user: User) = user !in hiddenUsers() + fun isHidden(user: User) = user in hiddenUsers() fun isAcceptable(user: User): Boolean { return user !in hiddenUsers() // if user hasn't hided this author diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index a87d8eeef..9d349ba5c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -72,8 +72,6 @@ object LocalCache { // new event val oldUser = getOrCreateUser(event.pubKey.toHexKey()) if (event.createdAt > oldUser.updatedMetadataAt) { - //Log.d("MT", "New User ${users.size} ${event.contactMetaData.name}") - val newUser = try { metadataParser.readValue(ByteArrayInputStream(event.content.toByteArray(Charsets.UTF_8)), UserMetadata::class.java) } catch (e: Exception) { @@ -84,6 +82,8 @@ object LocalCache { oldUser.updateUserInfo(newUser, event.createdAt) oldUser.latestMetadata = event + + //Log.d("MT", "New User Metadata ${oldUser.pubkeyDisplayHex} ${oldUser.toBestDisplayName()}") } else { //Log.d("MT","Relay sent a previous Metadata Event ${oldUser.toBestDisplayName()} ${formattedDateTime(event.createdAt)} > ${formattedDateTime(oldUser.updatedAt)}") } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index e94a8e019..5bff843c2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -43,6 +43,8 @@ class Note(val idHex: String) { var channel: Channel? = null + var lastDownloadTime: Long? = null + fun loadEvent(event: Event, author: User, mentions: List, replyTo: MutableList) { this.event = event this.author = author @@ -128,7 +130,7 @@ class Note(val idHex: String) { val returningList = mutableSetOf() while (matcher.find()) { try { - val tag = event?.tags?.get(matcher.group(1).toInt()) + val tag = matcher.group(1)?.let { event?.tags?.get(it.toInt()) } if (tag != null && tag[0] == "p") { returningList.add(LocalCache.getOrCreateUser(tag[1])) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index f4049aa8d..904734c4a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -64,8 +64,8 @@ class User(val pubkeyHex: String) { follows.add(user) user.followers.add(this) - invalidateData() - user.invalidateData() + invalidateData(liveFollows) + user.invalidateData(liveFollows) listeners.forEach { it.onFollowsChange() @@ -75,8 +75,8 @@ class User(val pubkeyHex: String) { follows.remove(user) user.followers.remove(this) - invalidateData() - user.invalidateData() + invalidateData(liveFollows) + user.invalidateData(liveFollows) updateSubscribers { it.onFollowsChange() @@ -91,7 +91,7 @@ class User(val pubkeyHex: String) { fun addReport(note: Note) { if (reports.add(note)) { updateSubscribers { it.onNewReports() } - invalidateData() + invalidateData(liveReports) } } @@ -118,7 +118,7 @@ class User(val pubkeyHex: String) { fun addMessage(user: User, msg: Note) { getOrCreateChannel(user).add(msg) - invalidateData() + invalidateData(liveMessages) updateSubscribers { it.onNewMessage() } } @@ -140,6 +140,7 @@ class User(val pubkeyHex: String) { } updateSubscribers { it.onNewRelayInfo() } + invalidateData(liveRelayInfo) } fun updateFollows(newFollows: Set, updateAt: Long) { @@ -167,15 +168,14 @@ class User(val pubkeyHex: String) { } } - invalidateData() + invalidateData(liveRelays) } fun updateUserInfo(newUserInfo: UserMetadata, updateAt: Long) { info = newUserInfo updatedMetadataAt = updateAt - - invalidateData() + invalidateData(liveMetadata) } fun isFollowing(user: User): Boolean { @@ -242,16 +242,21 @@ class User(val pubkeyHex: String) { } // UI Observers line up here. - val live: UserLiveData = UserLiveData(this) + val liveFollows: UserLiveData = UserLiveData(this) + val liveReports: UserLiveData = UserLiveData(this) + val liveMessages: UserLiveData = UserLiveData(this) + val liveRelays: UserLiveData = UserLiveData(this) + val liveRelayInfo: UserLiveData = UserLiveData(this) + val liveMetadata: UserLiveData = UserLiveData(this) // Refreshes observers in batches. var handlerWaiting = false @Synchronized - fun invalidateData() { + fun invalidateData(live: UserLiveData) { if (handlerWaiting) return handlerWaiting = true - val scope = CoroutineScope(Job() + Dispatchers.Default) + val scope = CoroutineScope(Job() + Dispatchers.Main) scope.launch { delay(100) live.refresh() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/Channel.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/Channel.kt index 1f6d5296c..35d5e5759 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/Channel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/Channel.kt @@ -7,4 +7,9 @@ data class Channel ( val id: String = UUID.randomUUID().toString().substring(0,4) ) { var filter: List? = null // Inactive when null + private var lastEOSE: Long? = null + + fun updateEOSE(l: Long) { + lastEOSE = l + } } \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt index 8030ee764..9f71e8333 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt @@ -16,6 +16,7 @@ import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.amethyst.service.relays.Relay import java.util.Collections +import java.util.Date import java.util.UUID import kotlin.time.ExperimentalTime import kotlin.time.measureTimedValue @@ -90,7 +91,7 @@ abstract class NostrDataSource(val debugName: String) { //Log.e("ERROR", "Relay ${relay.url}: ${error.message}") } - override fun onRelayStateChange(type: Relay.Type, relay: Relay) { + override fun onRelayStateChange(type: Relay.Type, relay: Relay, channel: String?) { //Log.d("RELAY", "Relay ${relay.url} ${when (type) { // Relay.Type.CONNECT -> "connected." // Relay.Type.DISCONNECT -> "disconnected." @@ -98,11 +99,10 @@ abstract class NostrDataSource(val debugName: String) { // Relay.Type.EOSE -> "sent all events it had stored." //}}") - /* - if (type == Relay.Type.EOSE) { - // One everything is loaded, if new users are found, update filters - resetFilters() - }*/ + if (type == Relay.Type.EOSE && channel != null) { + // updates a per subscripton since date + channels.filter { it.id == channel }.firstOrNull()?.updateEOSE(Date().time / 1000) + } } override fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt index 5aa09df91..990be887f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt @@ -14,7 +14,7 @@ object NostrSingleUserDataSource: NostrDataSource("SingleUserFeed") { fun createUserFilter(): List? { if (usersToWatch.isEmpty()) return null - return usersToWatch.map { + return usersToWatch.filter { LocalCache.getOrCreateUser(it).latestMetadata == null }.map { JsonFilter( kinds = listOf(MetadataEvent.kind), authors = listOf(it), diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt index 5881bb175..4144c6111 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Client.kt @@ -72,8 +72,8 @@ object Client: RelayPool.Listener { listeners.forEach { it.onError(error, subscriptionId, relay) } } - override fun onRelayStateChange(type: Relay.Type, relay: Relay) { - listeners.forEach { it.onRelayStateChange(type, relay) } + override fun onRelayStateChange(type: Relay.Type, relay: Relay, channel: String?) { + listeners.forEach { it.onRelayStateChange(type, relay, channel) } } override fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) { @@ -112,7 +112,7 @@ object Client: RelayPool.Listener { /** * Connected to or disconnected from a relay */ - open fun onRelayStateChange(type: Relay.Type, relay: Relay) = Unit + open fun onRelayStateChange(type: Relay.Type, relay: Relay, channel: String?) = Unit /** * When an relay saves or rejects a new event. diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt index 6cc7e31c5..8eb784c3f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/Relay.kt @@ -2,11 +2,8 @@ package com.vitorpamplona.amethyst.service.relays import android.util.Log import com.google.gson.JsonElement -import com.vitorpamplona.amethyst.model.LocalCache import java.util.Date -import nostr.postr.events.ContactListEvent import nostr.postr.events.Event -import nostr.postr.toNpub import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response @@ -50,7 +47,7 @@ class Relay( Client.allSubscriptions().forEach { sendFilter(requestId = it) } - listeners.forEach { it.onRelayStateChange(this@Relay, Type.CONNECT) } + listeners.forEach { it.onRelayStateChange(this@Relay, Type.CONNECT, null) } } override fun onMessage(webSocket: WebSocket, text: String) { @@ -65,7 +62,7 @@ class Relay( listeners.forEach { it.onEvent(this@Relay, channel, event) } } "EOSE" -> listeners.forEach { - it.onRelayStateChange(this@Relay, Type.EOSE) + it.onRelayStateChange(this@Relay, Type.EOSE, channel) } "NOTICE" -> listeners.forEach { // "channel" being the second string in the string array ... @@ -91,13 +88,17 @@ class Relay( } override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { - listeners.forEach { it.onRelayStateChange(this@Relay, Type.DISCONNECTING) } + listeners.forEach { it.onRelayStateChange( + this@Relay, + Type.DISCONNECTING, + null + ) } } override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { socket = null closingTime = Date().time / 1000 - listeners.forEach { it.onRelayStateChange(this@Relay, Type.DISCONNECT) } + listeners.forEach { it.onRelayStateChange(this@Relay, Type.DISCONNECT, null) } } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { @@ -192,6 +193,6 @@ class Relay( * * @param type is 0 for disconnect and 1 for connect */ - fun onRelayStateChange(relay: Relay, type: Type) + fun onRelayStateChange(relay: Relay, type: Type, channel: String?) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt index ee3a3670d..0466120f8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/relays/RelayPool.kt @@ -2,7 +2,6 @@ package com.vitorpamplona.amethyst.service.relays import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.Constants -import java.util.Collections import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -88,7 +87,7 @@ object RelayPool: Relay.Listener { fun onError(error: Error, subscriptionId: String, relay: Relay) - fun onRelayStateChange(type: Relay.Type, relay: Relay) + fun onRelayStateChange(type: Relay.Type, relay: Relay, channel: String?) fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) } @@ -103,8 +102,8 @@ object RelayPool: Relay.Listener { refreshObservers() } - override fun onRelayStateChange(relay: Relay, type: Relay.Type) { - listeners.forEach { it.onRelayStateChange(type, relay) } + override fun onRelayStateChange(relay: Relay, type: Relay.Type, channel: String?) { + listeners.forEach { it.onRelayStateChange(type, relay, channel) } refreshObservers() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt index a40a4aa41..be2b099f7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt @@ -24,7 +24,8 @@ fun ClickableRoute( ) { if (nip19.type == Nip19.Type.USER) { val userBase = LocalCache.getOrCreateUser(nip19.hex) - val userState by userBase.live.observeAsState() + + val userState by userBase.liveMetadata.observeAsState() val user = userState?.user ?: return val route = "User/${nip19.hex}" diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUserTag.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUserTag.kt index 550ce7e67..5e356091f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUserTag.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableUserTag.kt @@ -15,7 +15,7 @@ fun ClickableUserTag( user: User, navController: NavController ) { - val innerUserState by user.live.observeAsState() + val innerUserState by user.liveMetadata.observeAsState() ClickableText( text = AnnotatedString("@${innerUserState?.user?.toBestDisplayName()} "), onClick = { navController.navigate("User/${innerUserState?.user?.pubkeyHex}") }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index fe7820a2e..6c1bf9d57 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -77,7 +77,7 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return - val accountUserState by account.userProfile().live.observeAsState() + val accountUserState by account.userProfile().liveMetadata.observeAsState() val accountUser = accountUserState?.user val relayViewModel: RelayPoolViewModel = viewModel { RelayPoolViewModel() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index ead65627a..92c65b7af 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -81,52 +81,26 @@ fun DrawerContent(navController: NavHostController, val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return - val accountUserState by account.userProfile().live.observeAsState() - val accountUser = accountUserState?.user ?: return - Surface( modifier = Modifier.fillMaxWidth(), color = MaterialTheme.colors.background ) { Column() { - Box { - val banner = accountUser?.info?.banner - if (banner != null && banner.isNotBlank()) { - AsyncImage( - model = banner, - contentDescription = "Profile Image", - contentScale = ContentScale.FillWidth, - modifier = Modifier - .fillMaxWidth() - .height(150.dp) - ) - } else { - Image( - painter = painterResource(R.drawable.profile_banner), - contentDescription = "Profile Banner", - contentScale = ContentScale.FillWidth, - modifier = Modifier - .fillMaxWidth() - .height(150.dp) - ) - } - - ProfileContent( - accountUser, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 25.dp) - .padding(top = 100.dp), - scaffoldState, - navController - ) - } + ProfileContent( + account.userProfile(), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 25.dp) + .padding(top = 100.dp), + scaffoldState, + navController + ) Divider( thickness = 0.25.dp, modifier = Modifier.padding(top = 20.dp) ) ListContent( - accountUser, + account.userProfile(), navController, scaffoldState, modifier = Modifier @@ -135,50 +109,79 @@ fun DrawerContent(navController: NavHostController, accountStateViewModel ) - BottomContent(accountUser, scaffoldState, navController) + BottomContent(account.userProfile(), scaffoldState, navController) } } } @Composable -fun ProfileContent(accountUser: User?, modifier: Modifier = Modifier, scaffoldState: ScaffoldState, navController: NavController) { +fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffoldState: ScaffoldState, navController: NavController) { val coroutineScope = rememberCoroutineScope() - Column(modifier = modifier) { - AsyncImage( - model = accountUser?.profilePicture() ?: "https://robohash.org/ohno.png", - contentDescription = "Profile Image", - placeholder = rememberAsyncImagePainter("https://robohash.org/${accountUser?.pubkeyHex}.png"), - modifier = Modifier - .width(100.dp) - .height(100.dp) - .clip(shape = CircleShape) - .border(3.dp, MaterialTheme.colors.background, CircleShape) - .background(MaterialTheme.colors.background) - .clickable(onClick = { - accountUser?.let { - navController.navigate("User/${it.pubkeyHex}") - } - coroutineScope.launch { - scaffoldState.drawerState.close() - } - }) - ) - Text( - accountUser?.bestDisplayName() ?: "", - modifier = Modifier.padding(top = 7.dp), - fontWeight = FontWeight.Bold, - fontSize = 18.sp - ) - Text(" @${accountUser?.bestUsername()}", color = Color.LightGray) - Row(modifier = Modifier.padding(top = 15.dp)) { - Row() { - Text("${accountUser?.follows?.size ?: "--"}", fontWeight = FontWeight.Bold) - Text(" Following") - } - Row(modifier = Modifier.padding(start = 10.dp)) { - Text("${accountUser?.followers?.size ?: "--"}", fontWeight = FontWeight.Bold) - Text(" Followers") + val accountUserState by baseAccountUser.liveMetadata.observeAsState() + val accountUser = accountUserState?.user ?: return + + val accountUserFollowsState by baseAccountUser.liveFollows.observeAsState() + val accountUserFollows = accountUserFollowsState?.user ?: return + + Box { + val banner = accountUser.info.banner + if (banner != null && banner.isNotBlank()) { + AsyncImage( + model = banner, + contentDescription = "Profile Image", + contentScale = ContentScale.FillWidth, + modifier = Modifier + .fillMaxWidth() + .height(150.dp) + ) + } else { + Image( + painter = painterResource(R.drawable.profile_banner), + contentDescription = "Profile Banner", + contentScale = ContentScale.FillWidth, + modifier = Modifier + .fillMaxWidth() + .height(150.dp) + ) + } + + Column(modifier = modifier) { + AsyncImage( + model = accountUser.profilePicture(), + contentDescription = "Profile Image", + placeholder = rememberAsyncImagePainter("https://robohash.org/${accountUser.pubkeyHex}.png"), + modifier = Modifier + .width(100.dp) + .height(100.dp) + .clip(shape = CircleShape) + .border(3.dp, MaterialTheme.colors.background, CircleShape) + .background(MaterialTheme.colors.background) + .clickable(onClick = { + accountUser.let { + navController.navigate("User/${it.pubkeyHex}") + } + coroutineScope.launch { + scaffoldState.drawerState.close() + } + }) + ) + Text( + accountUser.bestDisplayName() ?: "", + modifier = Modifier.padding(top = 7.dp), + fontWeight = FontWeight.Bold, + fontSize = 18.sp + ) + Text(" @${accountUser.bestUsername()}", color = Color.LightGray) + Row(modifier = Modifier.padding(top = 15.dp)) { + Row() { + Text("${accountUserFollows.follows?.size ?: "--"}", fontWeight = FontWeight.Bold) + Text(" Following") + } + Row(modifier = Modifier.padding(start = 10.dp)) { + Text("${accountUserFollows.followers?.size ?: "--"}", fontWeight = FontWeight.Bold) + Text(" Followers") + } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt index ee1c436ff..14e5d4af4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomCompose.kt @@ -53,9 +53,6 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return - val accountUserState by account.userProfile().live.observeAsState() - val accountUser = accountUserState?.user ?: return - val notificationCacheState = NotificationCache.live.observeAsState() val notificationCache = notificationCacheState.value ?: return @@ -64,7 +61,7 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr if (note?.event == null) { BlankNote(Modifier) } else if (note.channel != null) { - val authorState by note.author!!.live.observeAsState() + val authorState by note.author!!.liveMetadata.observeAsState() val author = authorState?.user val channelState by note.channel!!.live.observeAsState() @@ -108,25 +105,19 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr } } else { - val authorState by note.author!!.live.observeAsState() - val author = authorState?.user - val replyAuthorBase = note.mentions?.first() - var userToComposeOn = author + var userToComposeOn = note.author!! - if ( replyAuthorBase != null ) { - val replyAuthorState by replyAuthorBase.live.observeAsState() - val replyAuthor = replyAuthorState?.user - - if (author == accountUser) { - userToComposeOn = replyAuthor + if (replyAuthorBase != null) { + if (note.author == account.userProfile()) { + userToComposeOn = replyAuthorBase } } val noteEvent = note.event - userToComposeOn?.let { user -> + userToComposeOn.let { user -> val hasNewMessages = if (noteEvent != null) noteEvent.createdAt > notificationCache.cache.load("Room/${userToComposeOn.pubkeyHex}", context) @@ -134,8 +125,8 @@ fun ChatroomCompose(baseNote: Note, accountViewModel: AccountViewModel, navContr false ChannelName( - channelPicture = { UserPicture(user, accountUser, size = 55.dp) }, - channelTitle = { UsernameDisplay(user, it) }, + channelPicture = { UserPicture(userToComposeOn, account.userProfile(), size = 55.dp) }, + channelTitle = { UsernameDisplay(userToComposeOn, it) }, channelLastTime = noteEvent?.createdAt, channelLastContent = accountViewModel.decrypt(note), hasNewMessages = hasNewMessages, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt index 076b4c2f0..e00364971 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ChatroomMessageCompose.kt @@ -59,8 +59,7 @@ fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return - val accountUserState by account.userProfile().live.observeAsState() - val accountUser = accountUserState?.user + val accountUser = account.userProfile() var popupExpanded by remember { mutableStateOf(false) } @@ -69,14 +68,11 @@ fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote if (note?.event == null) { BlankNote(Modifier) } else { - val authorState by note.author!!.live.observeAsState() - val author = authorState?.user - var backgroundBubbleColor: Color var alignment: Arrangement.Horizontal var shape: Shape - if (author == accountUser) { + if (note.author == accountUser) { backgroundBubbleColor = MaterialTheme.colors.primary.copy(alpha = 0.32f) alignment = Arrangement.End shape = ChatBubbleShapeMe @@ -125,6 +121,9 @@ fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 5.dp), ) { + val authorState by note.author!!.liveMetadata.observeAsState() + val author = authorState?.user + if (innerQuote || author != accountUser && note.event is ChannelMessageEvent) { Row( verticalAlignment = Alignment.CenterVertically, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 8fb185fa3..e1051f8d7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -339,10 +339,7 @@ fun UserPicture( pictureModifier: Modifier = Modifier, onClick: ((User) -> Unit)? = null ) { - val accountState by baseUserAccount.live.observeAsState() - val accountUser = accountState?.user ?: return - - val userState by baseUser.live.observeAsState() + val userState by baseUser.liveMetadata.observeAsState() val user = userState?.user ?: return Box( @@ -367,6 +364,9 @@ fun UserPicture( ) + val accountState by baseUserAccount.liveFollows.observeAsState() + val accountUser = accountState?.user ?: return + if (accountUser.isFollowing(user) || user == accountUser) { Box( Modifier diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt index df0086235..4f8467343 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/ReplyInformation.kt @@ -44,7 +44,7 @@ fun ReplyInformation(replyTo: MutableList?, mentions: List?, prefix: val mentionSet = mentions.toSet() mentionSet.toSet().forEachIndexed { idx, user -> - val innerUserState by user.live.observeAsState() + val innerUserState by user.liveMetadata.observeAsState() val innerUser = innerUserState?.user innerUser?.let { myUser -> @@ -123,7 +123,7 @@ fun ReplyInformationChannel(replyTo: MutableList?, val mentionSet = mentions.toSet() mentionSet.forEachIndexed { idx, user -> - val innerUserState by user.live.observeAsState() + val innerUserState by user.liveMetadata.observeAsState() val innerUser = innerUserState?.user innerUser?.let { myUser -> diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt index 82af8d053..c84978db9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt @@ -34,14 +34,11 @@ fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navControlle val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return - val userState by baseUser.live.observeAsState() - val user = userState?.user ?: return - val ctx = LocalContext.current.applicationContext Column(modifier = Modifier.clickable( - onClick = { navController.navigate("User/${user.pubkeyHex}") } + onClick = { navController.navigate("User/${baseUser.pubkeyHex}") } ) ) { Row( @@ -59,6 +56,9 @@ fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navControlle UsernameDisplay(baseUser) } + val userState by baseUser.liveMetadata.observeAsState() + val user = userState?.user ?: return + Text( user.info.about ?: "", color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), @@ -68,15 +68,15 @@ fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navControlle } Column(modifier = Modifier.padding(start = 10.dp)) { - if (account?.isHidden(user) == false) { + if (account.isHidden(baseUser)) { ShowUserButton { - account.showUser(user.pubkeyHex) + account.showUser(baseUser.pubkeyHex) LocalPreferences(ctx).saveToEncryptedStorage(account) } - } else if (account?.userProfile()?.isFollowing(user) == true) { - UnfollowButton { account.unfollow(user) } + } else if (account.userProfile().isFollowing(baseUser)) { + UnfollowButton { account.unfollow(baseUser) } } else { - FollowButton { account?.follow(user) } + FollowButton { account.follow(baseUser) } } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt index 604830125..ab4ab6779 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UsernameDisplay.kt @@ -26,7 +26,7 @@ fun NoteUsernameDisplay(baseNote: Note, weight: Modifier = Modifier) { @Composable fun UsernameDisplay(baseUser: User, weight: Modifier = Modifier) { - val userState by baseUser.live.observeAsState() + val userState by baseUser.liveMetadata.observeAsState() val user = userState?.user ?: return if (user.bestUsername() != null || user.bestDisplayName() != null) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt index 32d0e7f5d..6ff74321c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt @@ -172,9 +172,6 @@ fun NoteMaster(baseNote: Note, accountViewModel: AccountViewModel, navController } else if (!account.isAcceptable(noteForReports)) { HiddenNote() } else { - val authorState by note.author!!.live.observeAsState() - val author = authorState?.user - Column( Modifier .fillMaxWidth() @@ -182,7 +179,7 @@ fun NoteMaster(baseNote: Note, accountViewModel: AccountViewModel, navController Row(modifier = Modifier .padding(start = 12.dp, end = 12.dp) .clickable(onClick = { - author?.let { + note.author?.let { navController.navigate("User/${it.pubkeyHex}") } }) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt index 785ea70d5..f918d30de 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt @@ -41,6 +41,7 @@ import coil.compose.rememberAsyncImagePainter import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.NostrChatRoomDataSource import com.vitorpamplona.amethyst.ui.actions.PostButton +import com.vitorpamplona.amethyst.ui.note.UserPicture import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -70,13 +71,18 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr } Column( - modifier = Modifier.fillMaxHeight().padding(vertical = 0.dp).weight(1f, true) + modifier = Modifier + .fillMaxHeight() + .padding(vertical = 0.dp) + .weight(1f, true) ) { ChatroomFeedView(feedViewModel, accountViewModel, navController, "Room/${userId}") } //LAST ROW - Row(modifier = Modifier.padding(10.dp).fillMaxWidth(), + Row(modifier = Modifier + .padding(10.dp) + .fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -118,22 +124,23 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr @Composable fun ChatroomHeader(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { - val authorState by baseUser.live.observeAsState() - val author = authorState?.user - Column(modifier = Modifier.clickable( - onClick = { navController.navigate("User/${author?.pubkeyHex}") } + onClick = { navController.navigate("User/${baseUser.pubkeyHex}") } ) ) { Column(modifier = Modifier.padding(12.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { + val authorState by baseUser.liveMetadata.observeAsState() + val author = authorState?.user + AsyncImage( model = author?.profilePicture(), placeholder = rememberAsyncImagePainter("https://robohash.org/${author?.pubkeyHex}.png"), contentDescription = "Profile Image", modifier = Modifier - .width(35.dp).height(35.dp) + .width(35.dp) + .height(35.dp) .clip(shape = CircleShape) ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index f94e44f02..d7d2830af 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -94,86 +94,92 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro val accountState by accountViewModel.accountLiveData.observeAsState() val account = accountState?.account ?: return - val accountUserState by account.userProfile().live.observeAsState() - val accountUser = accountUserState?.user + if (userId == null) return - if (userId != null && accountUser != null) { - DisposableEffect(account) { - NostrUserProfileDataSource.loadUserProfile(userId) - NostrUserProfileFollowersDataSource.loadUserProfile(userId) - NostrUserProfileFollowsDataSource.loadUserProfile(userId) + DisposableEffect(account) { + NostrUserProfileDataSource.loadUserProfile(userId) + NostrUserProfileFollowersDataSource.loadUserProfile(userId) + NostrUserProfileFollowsDataSource.loadUserProfile(userId) - onDispose { - NostrUserProfileDataSource.stop() - NostrUserProfileFollowsDataSource.stop() - NostrUserProfileFollowersDataSource.stop() - } + onDispose { + NostrUserProfileDataSource.stop() + NostrUserProfileFollowsDataSource.stop() + NostrUserProfileFollowersDataSource.stop() } + } - val baseUser = NostrUserProfileDataSource.user ?: return + val baseUser = NostrUserProfileDataSource.user ?: return - val userState by baseUser.live.observeAsState() - val user = userState?.user ?: return + Surface( + modifier = Modifier.fillMaxWidth(), + color = MaterialTheme.colors.background + ) { + Column() { + ProfileHeader(baseUser, navController, account, accountViewModel) - Surface( - modifier = Modifier.fillMaxWidth(), - color = MaterialTheme.colors.background - ) { - Column() { - ProfileHeader(user, navController, account, accountUser, accountViewModel) + val pagerState = rememberPagerState() + val coroutineScope = rememberCoroutineScope() - val pagerState = rememberPagerState() - val coroutineScope = rememberCoroutineScope() - - Column(modifier = Modifier.padding()) { - TabRow( - selectedTabIndex = pagerState.currentPage, - indicator = { tabPositions -> - TabRowDefaults.Indicator( - Modifier.pagerTabIndicatorOffset(pagerState, tabPositions), - color = MaterialTheme.colors.primary - ) - }, - ) { - Tab( - selected = pagerState.currentPage == 0, - onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } }, - text = { - Text(text = "Notes") - } + Column(modifier = Modifier.padding()) { + TabRow( + selectedTabIndex = pagerState.currentPage, + indicator = { tabPositions -> + TabRowDefaults.Indicator( + Modifier.pagerTabIndicatorOffset(pagerState, tabPositions), + color = MaterialTheme.colors.primary ) - - Tab( - selected = pagerState.currentPage == 1, - onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } }, - text = { - Text(text = "${user.follows?.size ?: "--"} Follows") - } - ) - - Tab( - selected = pagerState.currentPage == 2, - onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } }, - text = { - Text(text = "${user.followers?.size ?: "--"} Followers") - } - ) - - Tab( - selected = pagerState.currentPage == 3, - onClick = { coroutineScope.launch { pagerState.animateScrollToPage(3) } }, - text = { - Text(text = "${user.relaysBeingUsed.size ?: "--"} / ${user.relays?.size ?: "--"} Relays") - } - ) - } - HorizontalPager(count = 4, state = pagerState) { - when (pagerState.currentPage) { - 0 -> TabNotes(user, accountViewModel, navController) - 1 -> TabFollows(user, accountViewModel, navController) - 2 -> TabFollowers(user, accountViewModel, navController) - 3 -> TabRelays(baseUser, accountViewModel, navController) + }, + ) { + Tab( + selected = pagerState.currentPage == 0, + onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } }, + text = { + Text(text = "Notes") } + ) + + Tab( + selected = pagerState.currentPage == 1, + onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } }, + text = { + val userState by baseUser.liveFollows.observeAsState() + val userFollows = userState?.user?.follows?.size ?: "--" + + Text(text = "$userFollows Follows") + } + ) + + Tab( + selected = pagerState.currentPage == 2, + onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } }, + text = { + val userState by baseUser.liveFollows.observeAsState() + val userFollows = userState?.user?.followers?.size ?: "--" + + Text(text = "$userFollows Followers") + } + ) + + Tab( + selected = pagerState.currentPage == 3, + onClick = { coroutineScope.launch { pagerState.animateScrollToPage(3) } }, + text = { + val userState by baseUser.liveRelays.observeAsState() + val userRelaysBeingUsed = userState?.user?.relaysBeingUsed?.size ?: "--" + + val userStateRelayInfo by baseUser.liveRelayInfo.observeAsState() + val userRelays = userStateRelayInfo?.user?.relays?.size ?: "--" + + Text(text = "$userRelaysBeingUsed / $userRelays Relays") + } + ) + } + HorizontalPager(count = 4, state = pagerState) { + when (pagerState.currentPage) { + 0 -> TabNotes(baseUser, accountViewModel, navController) + 1 -> TabFollows(baseUser, accountViewModel, navController) + 2 -> TabFollowers(baseUser, accountViewModel, navController) + 3 -> TabRelays(baseUser, accountViewModel, navController) } } } @@ -183,36 +189,19 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro @Composable private fun ProfileHeader( - user: User, + baseUser: User, navController: NavController, account: Account, - accountUser: User, accountViewModel: AccountViewModel ) { val ctx = LocalContext.current.applicationContext var popupExpanded by remember { mutableStateOf(false) } + val accountUserState by account.userProfile().liveFollows.observeAsState() + val accountUser = accountUserState?.user ?: return + Box { - val banner = user.info.banner - if (banner != null && banner.isNotBlank()) { - AsyncImage( - model = banner, - contentDescription = "Profile Image", - contentScale = ContentScale.FillWidth, - modifier = Modifier - .fillMaxWidth() - .height(125.dp) - ) - } else { - Image( - painter = painterResource(R.drawable.profile_banner), - contentDescription = "Profile Banner", - contentScale = ContentScale.FillWidth, - modifier = Modifier - .fillMaxWidth() - .height(125.dp) - ) - } + DrawBanner(baseUser) Box(modifier = Modifier .padding(horizontal = 10.dp) @@ -237,7 +226,7 @@ private fun ProfileHeader( contentDescription = "More Options", ) - UserProfileDropDownMenu(user, popupExpanded, { popupExpanded = false }, accountViewModel) + UserProfileDropDownMenu(baseUser, popupExpanded, { popupExpanded = false }, accountViewModel) } } @@ -255,7 +244,7 @@ private fun ProfileHeader( ) { UserPicture( - user, navController, account.userProfile(), 100.dp, + baseUser, navController, account.userProfile(), 100.dp, pictureModifier = Modifier.border( 3.dp, MaterialTheme.colors.background, @@ -268,52 +257,88 @@ private fun ProfileHeader( Row(modifier = Modifier .height(35.dp) .padding(bottom = 3.dp)) { - MessageButton(user, navController) + MessageButton(baseUser, navController) - if (accountUser == user && account.isWriteable()) { + if (accountUser == baseUser && account.isWriteable()) { NSecCopyButton(account) } - NPubCopyButton(user) + NPubCopyButton(baseUser) - if (accountUser == user) { + if (accountUser == baseUser) { EditButton(account) } else { - if (!account.isHidden(user)) { + if (account.isHidden(baseUser)) { ShowUserButton { - account.showUser(user.pubkeyHex) + account.showUser(baseUser.pubkeyHex) LocalPreferences(ctx).saveToEncryptedStorage(account) } - } else if (accountUser.isFollowing(user)) { - UnfollowButton { account.unfollow(user) } + } else if (accountUser.isFollowing(baseUser)) { + UnfollowButton { account.unfollow(baseUser) } } else { - FollowButton { account.follow(user) } + FollowButton { account.follow(baseUser) } } } } } - Text( - user.bestDisplayName() ?: "", - modifier = Modifier.padding(top = 7.dp), - fontWeight = FontWeight.Bold, - fontSize = 25.sp - ) - Text( - " @${user.bestUsername()}", - color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) - ) - Text( - "${user.info.about}", - color = MaterialTheme.colors.onSurface, - modifier = Modifier.padding(top = 5.dp, bottom = 5.dp) - ) + DrawAdditionalInfo(baseUser) Divider(modifier = Modifier.padding(top = 6.dp)) } } } +@Composable +private fun DrawAdditionalInfo(baseUser: User) { + val userState by baseUser.liveMetadata.observeAsState() + val user = userState?.user ?: return + + Text( + user.bestDisplayName() ?: "", + modifier = Modifier.padding(top = 7.dp), + fontWeight = FontWeight.Bold, + fontSize = 25.sp + ) + Text( + " @${user.bestUsername()}", + color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) + ) + Text( + "${user.info.about}", + color = MaterialTheme.colors.onSurface, + modifier = Modifier.padding(top = 5.dp, bottom = 5.dp) + ) +} + +@Composable +private fun DrawBanner(baseUser: User) { + val userState by baseUser.liveMetadata.observeAsState() + val user = userState?.user ?: return + + val banner = user.info.banner + + if (banner != null && banner.isNotBlank()) { + AsyncImage( + model = banner, + contentDescription = "Profile Image", + contentScale = ContentScale.FillWidth, + modifier = Modifier + .fillMaxWidth() + .height(125.dp) + ) + } else { + Image( + painter = painterResource(R.drawable.profile_banner), + contentDescription = "Profile Banner", + contentScale = ContentScale.FillWidth, + modifier = Modifier + .fillMaxWidth() + .height(125.dp) + ) + } +} + @Composable fun TabNotes(user: User, accountViewModel: AccountViewModel, navController: NavController) { val accountState by accountViewModel.accountLiveData.observeAsState() @@ -560,7 +585,7 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () -> if ( account.userProfile() != user) { Divider() - if (!account.isHidden(user)) { + if (account.isHidden(user)) { DropdownMenuItem(onClick = { user.let { accountViewModel.show( diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index 46482ae16..ffbfdfb9e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -210,9 +210,6 @@ fun UserLine( account: Account, onClick: () -> Unit ) { - val userState by baseUser.live.observeAsState() - val user = userState?.user ?: return - Column(modifier = Modifier .fillMaxWidth() .clickable(onClick = onClick) @@ -226,7 +223,7 @@ fun UserLine( ) ) { - UserPicture(user, account.userProfile(), 55.dp, Modifier, null) + UserPicture(baseUser, account.userProfile(), 55.dp, Modifier, null) Column( modifier = Modifier @@ -237,6 +234,9 @@ fun UserLine( UsernameDisplay(baseUser) } + val userState by baseUser.liveMetadata.observeAsState() + val user = userState?.user ?: return + Text( user.info.about?.take(100) ?: "", color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),