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