Restructuring follows observables
This commit is contained in:
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
@@ -30,6 +31,7 @@ import java.util.Locale
|
|||||||
private const val DEBUG_PLAINTEXT_PREFERENCES = false
|
private const val DEBUG_PLAINTEXT_PREFERENCES = false
|
||||||
private const val DEBUG_PREFERENCES_NAME = "debug_prefs"
|
private const val DEBUG_PREFERENCES_NAME = "debug_prefs"
|
||||||
|
|
||||||
|
@Immutable
|
||||||
data class AccountInfo(
|
data class AccountInfo(
|
||||||
val npub: String,
|
val npub: String,
|
||||||
val hasPrivKey: Boolean = false
|
val hasPrivKey: Boolean = false
|
||||||
|
|||||||
@@ -1004,10 +1004,10 @@ class Account(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateContactListTo(newContactList: ContactListEvent?) {
|
private fun updateContactListTo(newContactList: ContactListEvent?) {
|
||||||
if (newContactList?.unverifiedFollowKeySet().isNullOrEmpty()) return
|
if (newContactList == null || newContactList.tags.isEmpty()) return
|
||||||
|
|
||||||
// Events might be different objects, we have to compare their ids.
|
// Events might be different objects, we have to compare their ids.
|
||||||
if (backupContactList?.id != newContactList?.id) {
|
if (backupContactList?.id != newContactList.id) {
|
||||||
backupContactList = newContactList
|
backupContactList = newContactList
|
||||||
saveable.invalidateData()
|
saveable.invalidateData()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,15 +249,11 @@ class User(val pubkeyHex: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isFollowing(user: User): Boolean {
|
fun isFollowing(user: User): Boolean {
|
||||||
return latestContactList?.unverifiedFollowKeySet()?.toSet()?.let {
|
return latestContactList?.isTaggedUser(user.pubkeyHex) ?: false
|
||||||
return user.pubkeyHex in it
|
|
||||||
} ?: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isFollowingHashtag(tag: String): Boolean {
|
fun isFollowingHashtag(tag: String): Boolean {
|
||||||
return latestContactList?.unverifiedFollowTagSet()?.toSet()?.let {
|
return latestContactList?.isTaggedHash(tag) ?: false
|
||||||
return tag in it
|
|
||||||
} ?: false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isFollowingHashtagCached(tag: String): Boolean {
|
fun isFollowingHashtagCached(tag: String): Boolean {
|
||||||
@@ -272,11 +268,17 @@ class User(val pubkeyHex: String) {
|
|||||||
} ?: false
|
} ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isFollowingCached(userHex: String): Boolean {
|
||||||
|
return latestContactList?.verifiedFollowKeySet?.let {
|
||||||
|
return userHex in it
|
||||||
|
} ?: false
|
||||||
|
}
|
||||||
|
|
||||||
fun transientFollowCount(): Int? {
|
fun transientFollowCount(): Int? {
|
||||||
return latestContactList?.unverifiedFollowKeySet()?.size
|
return latestContactList?.unverifiedFollowKeySet()?.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun transientFollowerCount(): Int {
|
suspend fun transientFollowerCount(): Int {
|
||||||
return LocalCache.users.values.count { it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
|
return LocalCache.users.values.count { it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +294,7 @@ class User(val pubkeyHex: String) {
|
|||||||
return latestContactList?.verifiedFollowKeySet?.size
|
return latestContactList?.verifiedFollowKeySet?.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cachedFollowerCount(): Int {
|
suspend fun cachedFollowerCount(): Int {
|
||||||
return LocalCache.users.values.count { it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
|
return LocalCache.users.values.count { it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,13 +52,13 @@ import androidx.lifecycle.Lifecycle
|
|||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.note.ChannelName
|
import com.vitorpamplona.amethyst.ui.note.ChannelName
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
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.SearchBarViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchBarViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
@@ -72,9 +72,9 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun JoinUserOrChannelView(onClose: () -> Unit, account: Account, nav: (String) -> Unit) {
|
fun JoinUserOrChannelView(onClose: () -> Unit, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val searchBarViewModel: SearchBarViewModel = viewModel()
|
val searchBarViewModel: SearchBarViewModel = viewModel()
|
||||||
searchBarViewModel.account = account
|
searchBarViewModel.account = accountViewModel.account
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
@@ -115,7 +115,7 @@ fun JoinUserOrChannelView(onClose: () -> Unit, account: Account, nav: (String) -
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(15.dp))
|
Spacer(modifier = Modifier.height(15.dp))
|
||||||
|
|
||||||
RenderSeach(searchBarViewModel, account, nav)
|
RenderSeach(searchBarViewModel, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ fun JoinUserOrChannelView(onClose: () -> Unit, account: Account, nav: (String) -
|
|||||||
@Composable
|
@Composable
|
||||||
private fun RenderSeach(
|
private fun RenderSeach(
|
||||||
searchBarViewModel: SearchBarViewModel,
|
searchBarViewModel: SearchBarViewModel,
|
||||||
account: Account,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -273,7 +273,7 @@ private fun RenderSeach(
|
|||||||
) { _, item ->
|
) { _, item ->
|
||||||
UserComposeForChat(
|
UserComposeForChat(
|
||||||
item,
|
item,
|
||||||
account = account,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav
|
nav = nav
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -305,7 +305,7 @@ private fun RenderSeach(
|
|||||||
@Composable
|
@Composable
|
||||||
fun UserComposeForChat(
|
fun UserComposeForChat(
|
||||||
baseUser: User,
|
baseUser: User,
|
||||||
account: Account,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@@ -323,7 +323,7 @@ fun UserComposeForChat(
|
|||||||
),
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
UserPicture(baseUser, nav, account.userProfile(), 55.dp)
|
UserPicture(baseUser, nav, accountViewModel, 55.dp)
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
|
Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ import androidx.compose.ui.window.Dialog
|
|||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewChannelView(onClose: () -> Unit, account: Account, channel: Channel? = null) {
|
fun NewChannelView(onClose: () -> Unit, accountViewModel: AccountViewModel, channel: Channel? = null) {
|
||||||
val postViewModel: NewChannelViewModel = viewModel()
|
val postViewModel: NewChannelViewModel = viewModel()
|
||||||
postViewModel.load(account, channel)
|
postViewModel.load(accountViewModel.account, channel)
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = { onClose() },
|
onDismissRequest = { onClose() },
|
||||||
|
|||||||
@@ -23,16 +23,16 @@ import androidx.compose.ui.window.Dialog
|
|||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.ui.components.*
|
import com.vitorpamplona.amethyst.ui.components.*
|
||||||
import com.vitorpamplona.amethyst.ui.note.ReplyInformation
|
import com.vitorpamplona.amethyst.ui.note.ReplyInformation
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, account: Account) {
|
fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, accountViewModel: AccountViewModel) {
|
||||||
val pollViewModel: NewPostViewModel = viewModel()
|
val pollViewModel: NewPostViewModel = viewModel()
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -40,7 +40,7 @@ fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
pollViewModel.load(account, baseReplyTo, quote)
|
pollViewModel.load(accountViewModel.account, baseReplyTo, quote)
|
||||||
delay(100)
|
delay(100)
|
||||||
|
|
||||||
pollViewModel.imageUploadingError.collect { error ->
|
pollViewModel.imageUploadingError.collect { error ->
|
||||||
@@ -108,7 +108,7 @@ fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
.verticalScroll(scrollState)
|
.verticalScroll(scrollState)
|
||||||
) {
|
) {
|
||||||
if (pollViewModel.replyTos != null && baseReplyTo?.event is TextNoteEvent) {
|
if (pollViewModel.replyTos != null && baseReplyTo?.event is TextNoteEvent) {
|
||||||
ReplyInformation(pollViewModel.replyTos, pollViewModel.mentions, account, "✖ ") {
|
ReplyInformation(pollViewModel.replyTos, pollViewModel.mentions, accountViewModel, "✖ ") {
|
||||||
pollViewModel.removeFromReplyList(it)
|
pollViewModel.removeFromReplyList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
userSuggestions,
|
userSuggestions,
|
||||||
key = { _, item -> item.pubkeyHex }
|
key = { _, item -> item.pubkeyHex }
|
||||||
) { _, item ->
|
) { _, item ->
|
||||||
UserLine(item, account) {
|
UserLine(item, accountViewModel) {
|
||||||
pollViewModel.autocompleteWithUser(item)
|
pollViewModel.autocompleteWithUser(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
userSuggestions,
|
userSuggestions,
|
||||||
key = { _, item -> item.pubkeyHex }
|
key = { _, item -> item.pubkeyHex }
|
||||||
) { _, item ->
|
) { _, item ->
|
||||||
UserLine(item, account) {
|
UserLine(item, accountViewModel) {
|
||||||
postViewModel.autocompleteWithUser(item)
|
postViewModel.autocompleteWithUser(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.JoinUserOrChannelView
|
import com.vitorpamplona.amethyst.ui.actions.JoinUserOrChannelView
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelFabColumn(account: Account, nav: (String) -> Unit) {
|
fun ChannelFabColumn(accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
var isOpen by remember {
|
var isOpen by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
@@ -35,11 +35,11 @@ fun ChannelFabColumn(account: Account, nav: (String) -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (wantsToCreateChannel) {
|
if (wantsToCreateChannel) {
|
||||||
NewChannelView({ wantsToCreateChannel = false }, account = account)
|
NewChannelView({ wantsToCreateChannel = false }, accountViewModel = accountViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wantsToJoinChannelOrUser) {
|
if (wantsToJoinChannelOrUser) {
|
||||||
JoinUserOrChannelView({ wantsToJoinChannelOrUser = false }, account = account, nav = nav)
|
JoinUserOrChannelView({ wantsToJoinChannelOrUser = false }, accountViewModel = accountViewModel, nav = nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column() {
|
Column() {
|
||||||
|
|||||||
@@ -19,17 +19,17 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewChannelButton(account: Account) {
|
fun NewChannelButton(accountViewModel: AccountViewModel) {
|
||||||
var wantsToPost by remember {
|
var wantsToPost by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wantsToPost) {
|
if (wantsToPost) {
|
||||||
NewChannelView({ wantsToPost = false }, account = account)
|
NewChannelView({ wantsToPost = false }, accountViewModel = accountViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ package com.vitorpamplona.amethyst.ui.dal
|
|||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
|
||||||
object HiddenAccountsFeedFilter : FeedFilter<User>() {
|
class HiddenAccountsFeedFilter(val account: Account) : FeedFilter<User>() {
|
||||||
lateinit var account: Account
|
|
||||||
|
|
||||||
override fun feed() = account.hiddenUsers()
|
override fun feed() = account.hiddenUsers()
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-11
@@ -4,18 +4,11 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
|
||||||
object UserProfileFollowersFeedFilter : FeedFilter<User>() {
|
class UserProfileFollowersFeedFilter(val user: User, val account: Account) : FeedFilter<User>() {
|
||||||
lateinit var account: Account
|
|
||||||
var user: User? = null
|
|
||||||
|
|
||||||
fun loadUserProfile(accountLoggedIn: Account, user: User?) {
|
|
||||||
account = accountLoggedIn
|
|
||||||
this.user = user
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun feed(): List<User> {
|
override fun feed(): List<User> {
|
||||||
return user?.let { myUser ->
|
return user.let { myUser ->
|
||||||
LocalCache.users.values.filter { it.isFollowing(myUser) && account.isAcceptable(it) }
|
LocalCache.users.values.filter { it.isFollowing(myUser) && !account.isHidden(it) }
|
||||||
} ?: emptyList()
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-9
@@ -3,21 +3,25 @@ package com.vitorpamplona.amethyst.ui.dal
|
|||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
||||||
|
|
||||||
object UserProfileFollowsFeedFilter : FeedFilter<User>() {
|
class UserProfileFollowsFeedFilter(val user: User, val account: Account) : FeedFilter<User>() {
|
||||||
lateinit var account: Account
|
|
||||||
var user: User? = null
|
|
||||||
|
|
||||||
fun loadUserProfile(accountLoggedIn: Account, user: User?) {
|
val cache: MutableMap<ContactListEvent, List<User>> = mutableMapOf()
|
||||||
account = accountLoggedIn
|
|
||||||
this.user = user
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun feed(): List<User> {
|
override fun feed(): List<User> {
|
||||||
return user?.latestContactList?.unverifiedFollowKeySet()?.mapNotNull {
|
val contactList = user.latestContactList ?: return emptyList()
|
||||||
|
|
||||||
|
val previousList = cache[contactList]
|
||||||
|
if (previousList != null) return previousList
|
||||||
|
|
||||||
|
cache[contactList] = user.latestContactList
|
||||||
|
?.unverifiedFollowKeySet()?.mapNotNull {
|
||||||
LocalCache.checkGetOrCreateUser(it)
|
LocalCache.checkGetOrCreateUser(it)
|
||||||
}?.toSet()
|
}?.toSet()
|
||||||
?.filter { account.isAcceptable(it) }
|
?.filter { !account.isHidden(it) }
|
||||||
?.reversed() ?: emptyList()
|
?.reversed() ?: emptyList()
|
||||||
|
|
||||||
|
return cache[contactList] ?: emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+148
-107
@@ -26,6 +26,8 @@ import androidx.compose.material.icons.filled.ArrowBack
|
|||||||
import androidx.compose.material.icons.filled.Logout
|
import androidx.compose.material.icons.filled.Logout
|
||||||
import androidx.compose.material.icons.filled.RadioButtonChecked
|
import androidx.compose.material.icons.filled.RadioButtonChecked
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -40,9 +42,11 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import com.vitorpamplona.amethyst.AccountInfo
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.decodePublicKey
|
import com.vitorpamplona.amethyst.model.decodePublicKey
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
@@ -52,6 +56,8 @@ import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage
|
import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AccountSwitchBottomSheet(
|
fun AccountSwitchBottomSheet(
|
||||||
@@ -59,11 +65,6 @@ fun AccountSwitchBottomSheet(
|
|||||||
accountStateViewModel: AccountStateViewModel
|
accountStateViewModel: AccountStateViewModel
|
||||||
) {
|
) {
|
||||||
val accounts = LocalPreferences.allSavedAccounts()
|
val accounts = LocalPreferences.allSavedAccounts()
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = accountState?.account ?: return
|
|
||||||
|
|
||||||
val accountUserState by account.userProfile().live().metadata.observeAsState()
|
|
||||||
val accountUser = accountUserState?.user ?: return
|
|
||||||
|
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
@@ -79,108 +80,7 @@ fun AccountSwitchBottomSheet(
|
|||||||
Text(stringResource(R.string.account_switch_select_account), fontWeight = FontWeight.Bold)
|
Text(stringResource(R.string.account_switch_select_account), fontWeight = FontWeight.Bold)
|
||||||
}
|
}
|
||||||
accounts.forEach { acc ->
|
accounts.forEach { acc ->
|
||||||
val current = accountUser.pubkeyNpub() == acc.npub
|
DisplayAccount(acc, accountViewModel, accountStateViewModel)
|
||||||
|
|
||||||
val baseUser = try {
|
|
||||||
LocalCache.getOrCreateUser(decodePublicKey(acc.npub).toHexKey())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (baseUser != null) {
|
|
||||||
val userState by baseUser.live().metadata.observeAsState()
|
|
||||||
val user = userState?.user ?: return
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.clickable {
|
|
||||||
accountStateViewModel.switchUser(acc.npub)
|
|
||||||
},
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp, 16.dp)
|
|
||||||
.weight(1f),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(55.dp)
|
|
||||||
.padding(0.dp)
|
|
||||||
) {
|
|
||||||
RobohashAsyncImageProxy(
|
|
||||||
robot = user.pubkeyHex,
|
|
||||||
model = ResizeImage(user.profilePicture(), 55.dp),
|
|
||||||
contentDescription = stringResource(R.string.profile_image),
|
|
||||||
modifier = Modifier
|
|
||||||
.width(55.dp)
|
|
||||||
.height(55.dp)
|
|
||||||
.clip(shape = CircleShape)
|
|
||||||
)/*
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(20.dp)
|
|
||||||
.align(Alignment.TopEnd)
|
|
||||||
) {
|
|
||||||
if (acc.hasPrivKey) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Key,
|
|
||||||
contentDescription = stringResource(R.string.account_switch_has_private_key),
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
tint = MaterialTheme.colors.primary
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Visibility,
|
|
||||||
contentDescription = stringResource(R.string.account_switch_pubkey_only),
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
tint = MaterialTheme.colors.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
|
||||||
val npubShortHex = acc.npub.toShortenHex()
|
|
||||||
|
|
||||||
user.bestDisplayName()?.let {
|
|
||||||
CreateTextWithEmoji(
|
|
||||||
text = it,
|
|
||||||
tags = user.info?.latestMetadata?.tags
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(npubShortHex)
|
|
||||||
}
|
|
||||||
Column(modifier = Modifier.width(32.dp)) {
|
|
||||||
if (current) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.RadioButtonChecked,
|
|
||||||
contentDescription = stringResource(R.string.account_switch_active_account),
|
|
||||||
tint = MaterialTheme.colors.secondary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IconButton(
|
|
||||||
onClick = { accountStateViewModel.logOff(acc.npub) }
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Logout,
|
|
||||||
contentDescription = stringResource(R.string.log_out),
|
|
||||||
tint = MaterialTheme.colors.onSurface
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -222,3 +122,144 @@ fun AccountSwitchBottomSheet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplayAccount(
|
||||||
|
acc: AccountInfo,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
accountStateViewModel: AccountStateViewModel
|
||||||
|
) {
|
||||||
|
var baseUser by remember { mutableStateOf<User?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = acc.npub) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
baseUser = try {
|
||||||
|
LocalCache.getOrCreateUser(decodePublicKey(acc.npub).toHexKey())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseUser?.let {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.clickable {
|
||||||
|
accountStateViewModel.switchUser(acc.npub)
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp, 16.dp)
|
||||||
|
.weight(1f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(55.dp)
|
||||||
|
.padding(0.dp)
|
||||||
|
) {
|
||||||
|
AccountPicture(it)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
AccountName(acc, it)
|
||||||
|
}
|
||||||
|
Column(modifier = Modifier.width(32.dp)) {
|
||||||
|
ActiveMarker(acc, accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogoutButton(acc, accountStateViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ActiveMarker(acc: AccountInfo, accountViewModel: AccountViewModel) {
|
||||||
|
val isCurrentUser by remember(accountViewModel) {
|
||||||
|
derivedStateOf {
|
||||||
|
accountViewModel.account.userProfile().pubkeyNpub() == acc.npub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCurrentUser) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.RadioButtonChecked,
|
||||||
|
contentDescription = stringResource(R.string.account_switch_active_account),
|
||||||
|
tint = MaterialTheme.colors.secondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AccountPicture(user: User) {
|
||||||
|
val userState by user.live().metadata.observeAsState()
|
||||||
|
val profilePicture by remember(userState) {
|
||||||
|
derivedStateOf {
|
||||||
|
ResizeImage(userState?.user?.profilePicture(), 55.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RobohashAsyncImageProxy(
|
||||||
|
robot = remember(user) { user.pubkeyHex },
|
||||||
|
model = profilePicture,
|
||||||
|
contentDescription = stringResource(R.string.profile_image),
|
||||||
|
modifier = Modifier
|
||||||
|
.width(55.dp)
|
||||||
|
.height(55.dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AccountName(
|
||||||
|
acc: AccountInfo,
|
||||||
|
user: User
|
||||||
|
) {
|
||||||
|
val userState by user.live().metadata.observeAsState()
|
||||||
|
val displayName by remember(userState) {
|
||||||
|
derivedStateOf {
|
||||||
|
user.bestDisplayName()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val tags by remember(userState) {
|
||||||
|
derivedStateOf {
|
||||||
|
user.info?.latestMetadata?.tags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName?.let {
|
||||||
|
CreateTextWithEmoji(
|
||||||
|
text = it,
|
||||||
|
tags = tags
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = remember(user) { acc.npub.toShortenHex() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LogoutButton(
|
||||||
|
acc: AccountInfo,
|
||||||
|
accountStateViewModel: AccountStateViewModel
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = { accountStateViewModel.logOff(acc.npub) }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Logout,
|
||||||
|
contentDescription = stringResource(R.string.log_out),
|
||||||
|
tint = MaterialTheme.colors.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -160,10 +160,6 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel)
|
|||||||
@OptIn(coil.annotation.ExperimentalCoilApi::class)
|
@OptIn(coil.annotation.ExperimentalCoilApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun GenericTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel, content: @Composable (AccountViewModel) -> Unit) {
|
fun GenericTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel, content: @Composable (AccountViewModel) -> Unit) {
|
||||||
val relayViewModel: RelayPoolViewModel = viewModel { RelayPoolViewModel() }
|
|
||||||
val connectedRelaysLiveData by relayViewModel.connectedRelaysLiveData.observeAsState()
|
|
||||||
val availableRelaysLiveData by relayViewModel.availableRelaysLiveData.observeAsState()
|
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
var wantsToEditRelays by remember {
|
var wantsToEditRelays by remember {
|
||||||
@@ -195,29 +191,9 @@ fun GenericTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewMod
|
|||||||
content(accountViewModel)
|
content(accountViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
RelayStatus(
|
||||||
modifier = Modifier
|
{ wantsToEditRelays = true }
|
||||||
.fillMaxWidth()
|
|
||||||
.fillMaxHeight(),
|
|
||||||
horizontalAlignment = Alignment.End
|
|
||||||
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxHeight(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
"${connectedRelaysLiveData ?: "--"}/${availableRelaysLiveData ?: "--"}",
|
|
||||||
color = if (connectedRelaysLiveData == 0) Color.Red else MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
||||||
style = MaterialTheme.typography.subtitle1,
|
|
||||||
modifier = Modifier.clickable(
|
|
||||||
onClick = {
|
|
||||||
wantsToEditRelays = true
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -229,8 +205,59 @@ fun GenericTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewMod
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
|
RelayIcon { wantsToEditRelays = true }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Divider(thickness = 0.25.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RelayStatus(
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val relayViewModel: RelayPoolViewModel = viewModel { RelayPoolViewModel() }
|
||||||
|
val connectedRelaysLiveData = relayViewModel.connectedRelaysLiveData.observeAsState()
|
||||||
|
val availableRelaysLiveData = relayViewModel.availableRelaysLiveData.observeAsState()
|
||||||
|
|
||||||
|
val connectedRelaysText by remember(connectedRelaysLiveData, availableRelaysLiveData) {
|
||||||
|
derivedStateOf {
|
||||||
|
"${connectedRelaysLiveData.value ?: "--"}/${availableRelaysLiveData.value ?: "--"}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight(),
|
||||||
|
horizontalAlignment = Alignment.End
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = connectedRelaysText,
|
||||||
|
color = if (connectedRelaysLiveData.value == 0) {
|
||||||
|
Color.Red
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colors.onSurface.copy(
|
||||||
|
alpha = 0.32f
|
||||||
|
)
|
||||||
|
},
|
||||||
|
style = MaterialTheme.typography.subtitle1,
|
||||||
|
modifier = Modifier.clickable(
|
||||||
|
onClick = onClick
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RelayIcon(onClick: () -> Unit) {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { wantsToEditRelays = true },
|
onClick = onClick,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -241,10 +268,6 @@ fun GenericTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewMod
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
Divider(thickness = 0.25.dp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LoggedInUserPictureDrawer(
|
private fun LoggedInUserPictureDrawer(
|
||||||
@@ -252,15 +275,17 @@ private fun LoggedInUserPictureDrawer(
|
|||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
val accountUserState by accountViewModel.account.userProfile().live().metadata.observeAsState()
|
val accountUserState by accountViewModel.account.userProfile().live().metadata.observeAsState()
|
||||||
val accountUser = accountUserState?.user ?: return
|
|
||||||
|
val pubkeyHex = remember { accountUserState?.user?.pubkeyHex ?: "" }
|
||||||
|
val profilePicture = remember(accountUserState) { ResizeImage(accountUserState?.user?.profilePicture(), 34.dp) }
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
) {
|
) {
|
||||||
RobohashAsyncImageProxy(
|
RobohashAsyncImageProxy(
|
||||||
robot = remember { accountUser.pubkeyHex },
|
robot = pubkeyHex,
|
||||||
model = remember(accountUser) { ResizeImage(accountUser.profilePicture(), 34.dp) },
|
model = profilePicture,
|
||||||
contentDescription = stringResource(id = R.string.profile_image),
|
contentDescription = stringResource(id = R.string.profile_image),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(34.dp)
|
.width(34.dp)
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import androidx.compose.material.Surface
|
|||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.TextButton
|
import androidx.compose.material.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
@@ -64,6 +65,7 @@ import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@@ -132,10 +134,6 @@ fun ProfileContent(
|
|||||||
val tags = remember(accountUserState) { accountUserState?.user?.info?.latestMetadata?.tags }
|
val tags = remember(accountUserState) { accountUserState?.user?.info?.latestMetadata?.tags }
|
||||||
val route = remember(accountUserState) { "User/${accountUserState?.user?.pubkeyHex}" }
|
val route = remember(accountUserState) { "User/${accountUserState?.user?.pubkeyHex}" }
|
||||||
|
|
||||||
val accountUserFollowsState by baseAccountUser.live().follows.observeAsState()
|
|
||||||
val followingCount = remember(accountUserFollowsState) { accountUserFollowsState?.user?.cachedFollowCount()?.toString() ?: "--" }
|
|
||||||
val followerCount = remember(accountUserFollowsState) { accountUserFollowsState?.user?.cachedFollowerCount()?.toString() ?: "--" }
|
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
if (profileBanner != null) {
|
if (profileBanner != null) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
@@ -221,6 +219,33 @@ fun ProfileContent(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
|
FollowingAndFollowerCounts(baseAccountUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FollowingAndFollowerCounts(baseAccountUser: User) {
|
||||||
|
val accountUserFollowsState by baseAccountUser.live().follows.observeAsState()
|
||||||
|
|
||||||
|
var followingCount by remember { mutableStateOf("--") }
|
||||||
|
var followerCount by remember { mutableStateOf("--") }
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = accountUserFollowsState) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
val newFollowing = accountUserFollowsState?.user?.cachedFollowCount()?.toString() ?: "--"
|
||||||
|
val newFollower = accountUserFollowsState?.user?.cachedFollowerCount()?.toString() ?: "--"
|
||||||
|
|
||||||
|
if (followingCount != newFollowing) {
|
||||||
|
followingCount = newFollowing
|
||||||
|
}
|
||||||
|
if (followerCount != newFollower) {
|
||||||
|
followerCount = newFollower
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row() {
|
Row() {
|
||||||
Text(
|
Text(
|
||||||
text = followingCount,
|
text = followingCount,
|
||||||
@@ -236,9 +261,6 @@ fun ProfileContent(
|
|||||||
Text(stringResource(R.string.followers))
|
Text(stringResource(R.string.followers))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -257,7 +279,11 @@ fun ListContent(
|
|||||||
var conectOrbotDialogOpen by remember { mutableStateOf(false) }
|
var conectOrbotDialogOpen by remember { mutableStateOf(false) }
|
||||||
var proxyPort = remember { mutableStateOf(account.proxyPort.toString()) }
|
var proxyPort = remember { mutableStateOf(account.proxyPort.toString()) }
|
||||||
|
|
||||||
Column(modifier = modifier.fillMaxHeight().verticalScroll(rememberScrollState())) {
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
if (accountUserPubKey != null) {
|
if (accountUserPubKey != null) {
|
||||||
NavigationRow(
|
NavigationRow(
|
||||||
title = stringResource(R.string.profile),
|
title = stringResource(R.string.profile),
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.vitorpamplona.amethyst.ui.note
|
|||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
@@ -16,14 +15,11 @@ import androidx.compose.material.OutlinedTextField
|
|||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
@@ -38,6 +34,7 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||||
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
class AddBountyAmountViewModel : ViewModel() {
|
class AddBountyAmountViewModel : ViewModel() {
|
||||||
private var account: Account? = null
|
private var account: Account? = null
|
||||||
@@ -75,16 +72,10 @@ class AddBountyAmountViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AddBountyAmountDialog(bounty: Note, account: Account, onClose: () -> Unit) {
|
fun AddBountyAmountDialog(bounty: Note, accountViewModel: AccountViewModel, onClose: () -> Unit) {
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val postViewModel: AddBountyAmountViewModel = viewModel()
|
val postViewModel: AddBountyAmountViewModel = viewModel()
|
||||||
|
postViewModel.load(accountViewModel.account, bounty)
|
||||||
LaunchedEffect(account) {
|
|
||||||
postViewModel.load(account, bounty)
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = { onClose() },
|
onDismissRequest = { onClose() },
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BlankNote(modifier: Modifier = Modifier, isQuote: Boolean = false, idHex: String? = null) {
|
fun BlankNote(modifier: Modifier = Modifier, isQuote: Boolean = false, idHex: String? = null) {
|
||||||
@@ -54,7 +54,14 @@ fun BlankNote(modifier: Modifier = Modifier, isQuote: Boolean = false, idHex: St
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HiddenNote(reports: Set<Note>, loggedIn: User, modifier: Modifier = Modifier, isQuote: Boolean = false, nav: (String) -> Unit, onClick: () -> Unit) {
|
fun HiddenNote(
|
||||||
|
reports: Set<Note>,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isQuote: Boolean = false,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
Row(modifier = Modifier.padding(horizontal = if (!isQuote) 12.dp else 6.dp)) {
|
Row(modifier = Modifier.padding(horizontal = if (!isQuote) 12.dp else 6.dp)) {
|
||||||
Column(modifier = Modifier.padding(start = if (!isQuote) 10.dp else 5.dp)) {
|
Column(modifier = Modifier.padding(start = if (!isQuote) 10.dp else 5.dp)) {
|
||||||
@@ -75,7 +82,7 @@ fun HiddenNote(reports: Set<Note>, loggedIn: User, modifier: Modifier = Modifier
|
|||||||
NoteAuthorPicture(
|
NoteAuthorPicture(
|
||||||
baseNote = it,
|
baseNote = it,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
userAccount = loggedIn,
|
accountViewModel = accountViewModel,
|
||||||
size = 35.dp
|
size = 35.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro
|
|||||||
NoteAuthorPicture(
|
NoteAuthorPicture(
|
||||||
baseNote = it,
|
baseNote = it,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
userAccount = account.userProfile(),
|
accountViewModel = accountViewModel,
|
||||||
size = 35.dp
|
size = 35.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ fun ChatroomCompose(
|
|||||||
channelPicture = {
|
channelPicture = {
|
||||||
UserPicture(
|
UserPicture(
|
||||||
userToComposeOn,
|
userToComposeOn,
|
||||||
accountViewModel.userProfile(),
|
accountViewModel = accountViewModel,
|
||||||
size = 55.dp
|
size = 55.dp
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ fun ChatroomMessageCompose(
|
|||||||
if (!isAcceptableAndCanPreview.first && !showHiddenNote) {
|
if (!isAcceptableAndCanPreview.first && !showHiddenNote) {
|
||||||
HiddenNote(
|
HiddenNote(
|
||||||
account.getRelevantReports(noteForReports),
|
account.getRelevantReports(noteForReports),
|
||||||
account.userProfile(),
|
accountViewModel,
|
||||||
Modifier,
|
Modifier,
|
||||||
innerQuote,
|
innerQuote,
|
||||||
nav,
|
nav,
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, route
|
|||||||
NoteAuthorPicture(
|
NoteAuthorPicture(
|
||||||
baseNote = it,
|
baseNote = it,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
userAccount = account.userProfile(),
|
accountViewModel = accountViewModel,
|
||||||
size = 35.dp
|
size = 35.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -277,10 +277,9 @@ private fun AuthorPictureAndComment(
|
|||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
var content by remember { mutableStateOf<Triple<User?, String?, String?>>(Triple(zapRequest.author, null, null)) }
|
var content by remember { mutableStateOf<Triple<User?, String?, String?>>(Triple(zapRequest.author, null, null)) }
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = zapRequest.idHex, key2 = zapEvent?.idHex) {
|
LaunchedEffect(key1 = zapRequest.idHex, key2 = zapEvent?.idHex) {
|
||||||
scope.launch(Dispatchers.Default) {
|
launch(Dispatchers.Default) {
|
||||||
(zapRequest.event as? LnZapRequestEvent)?.let {
|
(zapRequest.event as? LnZapRequestEvent)?.let {
|
||||||
val decryptedContent = accountViewModel.decryptZap(zapRequest)
|
val decryptedContent = accountViewModel.decryptZap(zapRequest)
|
||||||
val amount = (zapEvent?.event as? LnZapEvent)?.amount
|
val amount = (zapEvent?.event as? LnZapEvent)?.amount
|
||||||
@@ -438,22 +437,11 @@ fun FastNoteAuthorPicture(
|
|||||||
author.pubkeyHex
|
author.pubkeyHex
|
||||||
}
|
}
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val loggedInLiveFollows = remember(accountState) { accountState?.account?.userProfile()?.live()?.follows } ?: return
|
|
||||||
|
|
||||||
val accountFollowsState by loggedInLiveFollows.observeAsState()
|
|
||||||
|
|
||||||
val showFollowingMark by remember(accountFollowsState) {
|
|
||||||
derivedStateOf {
|
|
||||||
accountFollowsState?.user?.isFollowingCached(author) == true || (author.pubkeyHex == accountFollowsState?.user?.pubkeyHex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UserPicture(
|
UserPicture(
|
||||||
userHex = authorPubKey,
|
userHex = authorPubKey,
|
||||||
userPicture = profilePicture,
|
userPicture = profilePicture,
|
||||||
showFollowingMark = showFollowingMark,
|
|
||||||
size = size,
|
size = size,
|
||||||
modifier = pictureModifier
|
modifier = pictureModifier,
|
||||||
|
accountViewModel = accountViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||||
|
import androidx.compose.ui.Alignment.Companion.TopEnd
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
@@ -82,7 +83,6 @@ import coil.request.SuccessResult
|
|||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
import com.vitorpamplona.amethyst.NotificationCache
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
@@ -286,7 +286,7 @@ fun LoadedNoteCompose(
|
|||||||
if (showHiddenNote) {
|
if (showHiddenNote) {
|
||||||
HiddenNote(
|
HiddenNote(
|
||||||
state.relevantReports,
|
state.relevantReports,
|
||||||
accountViewModel.userProfile(),
|
accountViewModel,
|
||||||
modifier,
|
modifier,
|
||||||
isBoostedNote,
|
isBoostedNote,
|
||||||
nav,
|
nav,
|
||||||
@@ -332,17 +332,13 @@ fun NormalNote(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = remember(accountState) { accountState?.account } ?: return
|
|
||||||
val loggedIn = remember(accountState) { accountState?.account?.userProfile() } ?: return
|
|
||||||
|
|
||||||
val noteEvent = remember { baseNote.event }
|
val noteEvent = remember { baseNote.event }
|
||||||
val channelHex = remember { baseNote.channelHex() }
|
val channelHex = remember { baseNote.channelHex() }
|
||||||
|
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && channelHex != null) {
|
if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && channelHex != null) {
|
||||||
ChannelHeader(channelHex = channelHex, account = account, nav = nav)
|
ChannelHeader(channelHex = channelHex, accountViewModel = accountViewModel, nav = nav)
|
||||||
} else if (noteEvent is BadgeDefinitionEvent) {
|
} else if (noteEvent is BadgeDefinitionEvent) {
|
||||||
BadgeDisplay(baseNote = baseNote)
|
BadgeDisplay(baseNote = baseNote)
|
||||||
} else if (noteEvent is FileHeaderEvent) {
|
} else if (noteEvent is FileHeaderEvent) {
|
||||||
@@ -392,7 +388,7 @@ fun NormalNote(
|
|||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
routeFor(baseNote, loggedIn)?.let {
|
routeFor(baseNote, accountViewModel.userProfile())?.let {
|
||||||
nav(it)
|
nav(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,7 +410,7 @@ fun NormalNote(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (!isBoostedNote && !isQuotedNote) {
|
if (!isBoostedNote && !isQuotedNote) {
|
||||||
DrawAuthorImages(baseNote, loggedIn, nav)
|
DrawAuthorImages(baseNote, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@@ -426,7 +422,6 @@ fun NormalNote(
|
|||||||
FirstUserInfoRow(
|
FirstUserInfoRow(
|
||||||
baseNote = baseNote,
|
baseNote = baseNote,
|
||||||
showAuthorPicture = isQuotedNote,
|
showAuthorPicture = isQuotedNote,
|
||||||
account = account,
|
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav
|
nav = nav
|
||||||
)
|
)
|
||||||
@@ -434,7 +429,7 @@ fun NormalNote(
|
|||||||
if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) {
|
if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) {
|
||||||
SecondUserInfoRow(
|
SecondUserInfoRow(
|
||||||
baseNote,
|
baseNote,
|
||||||
account,
|
accountViewModel,
|
||||||
nav
|
nav
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -446,7 +441,6 @@ fun NormalNote(
|
|||||||
baseNote,
|
baseNote,
|
||||||
unPackReply,
|
unPackReply,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
account,
|
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
nav
|
nav
|
||||||
)
|
)
|
||||||
@@ -466,7 +460,7 @@ fun NormalNote(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is LongTextNoteEvent -> {
|
is LongTextNoteEvent -> {
|
||||||
RenderLongFormContent(baseNote, loggedIn, accountViewModel, nav)
|
RenderLongFormContent(baseNote, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
is BadgeAwardEvent -> {
|
is BadgeAwardEvent -> {
|
||||||
@@ -478,7 +472,7 @@ fun NormalNote(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is AudioTrackEvent -> {
|
is AudioTrackEvent -> {
|
||||||
RenderAudioTrack(baseNote, loggedIn, accountViewModel, nav)
|
RenderAudioTrack(baseNote, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
is PinListEvent -> {
|
is PinListEvent -> {
|
||||||
@@ -812,7 +806,7 @@ fun DisplayPeopleList(
|
|||||||
)
|
)
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
withContext(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
members = noteEvent.bookmarkedPeople().mapNotNull { hex ->
|
members = noteEvent.bookmarkedPeople().mapNotNull { hex ->
|
||||||
LocalCache.checkGetOrCreateUser(hex)
|
LocalCache.checkGetOrCreateUser(hex)
|
||||||
}.sortedBy { account.isFollowing(it) }.reversed()
|
}.sortedBy { account.isFollowing(it) }.reversed()
|
||||||
@@ -883,7 +877,7 @@ private fun RenderBadgeAward(
|
|||||||
Text(text = stringResource(R.string.award_granted_to))
|
Text(text = stringResource(R.string.award_granted_to))
|
||||||
|
|
||||||
LaunchedEffect(key1 = note) {
|
LaunchedEffect(key1 = note) {
|
||||||
withContext(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
awardees = noteEvent.awardees().mapNotNull { hex ->
|
awardees = noteEvent.awardees().mapNotNull { hex ->
|
||||||
LocalCache.checkGetOrCreateUser(hex)
|
LocalCache.checkGetOrCreateUser(hex)
|
||||||
}.sortedBy { account.isFollowing(it) }.reversed()
|
}.sortedBy { account.isFollowing(it) }.reversed()
|
||||||
@@ -902,7 +896,7 @@ private fun RenderBadgeAward(
|
|||||||
) {
|
) {
|
||||||
UserPicture(
|
UserPicture(
|
||||||
baseUser = user,
|
baseUser = user,
|
||||||
baseUserAccount = accountViewModel.userProfile(),
|
accountViewModel = accountViewModel,
|
||||||
size = 35.dp
|
size = 35.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1098,13 +1092,12 @@ fun PinListHeader(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun RenderAudioTrack(
|
private fun RenderAudioTrack(
|
||||||
note: Note,
|
note: Note,
|
||||||
loggedIn: User,
|
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val noteEvent = note.event as? AudioTrackEvent ?: return
|
val noteEvent = note.event as? AudioTrackEvent ?: return
|
||||||
|
|
||||||
AudioTrackHeader(noteEvent, note, loggedIn, nav)
|
AudioTrackHeader(noteEvent, accountViewModel, nav)
|
||||||
|
|
||||||
ReactionsRow(note, accountViewModel, nav)
|
ReactionsRow(note, accountViewModel, nav)
|
||||||
|
|
||||||
@@ -1117,13 +1110,12 @@ private fun RenderAudioTrack(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun RenderLongFormContent(
|
private fun RenderLongFormContent(
|
||||||
note: Note,
|
note: Note,
|
||||||
loggedIn: User,
|
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val noteEvent = note.event as? LongTextNoteEvent ?: return
|
val noteEvent = note.event as? LongTextNoteEvent ?: return
|
||||||
|
|
||||||
LongFormHeader(noteEvent, note, loggedIn)
|
LongFormHeader(noteEvent, note, accountViewModel)
|
||||||
|
|
||||||
ReactionsRow(note, accountViewModel, nav)
|
ReactionsRow(note, accountViewModel, nav)
|
||||||
|
|
||||||
@@ -1205,7 +1197,6 @@ private fun ReplyRow(
|
|||||||
note: Note,
|
note: Note,
|
||||||
unPackReply: Boolean,
|
unPackReply: Boolean,
|
||||||
backgroundColor: Color,
|
backgroundColor: Color,
|
||||||
account: Account,
|
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -1236,13 +1227,13 @@ private fun ReplyRow(
|
|||||||
nav = nav
|
nav = nav
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ReplyInformation(note.replyTo, noteEvent.mentions(), account, nav)
|
ReplyInformation(note.replyTo, noteEvent.mentions(), accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(5.dp))
|
Spacer(modifier = Modifier.height(5.dp))
|
||||||
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
|
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
|
||||||
note.channel()?.let {
|
note.channel()?.let {
|
||||||
ReplyInformationChannel(note.replyTo, noteEvent.mentions(), it, account, nav)
|
ReplyInformationChannel(note.replyTo, noteEvent.mentions(), it, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(5.dp))
|
Spacer(modifier = Modifier.height(5.dp))
|
||||||
@@ -1252,7 +1243,7 @@ private fun ReplyRow(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun SecondUserInfoRow(
|
private fun SecondUserInfoRow(
|
||||||
note: Note,
|
note: Note,
|
||||||
account: Account,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val noteEvent = remember { note.event } ?: return
|
val noteEvent = remember { note.event } ?: return
|
||||||
@@ -1263,7 +1254,7 @@ private fun SecondUserInfoRow(
|
|||||||
|
|
||||||
val baseReward = remember { noteEvent.getReward() }
|
val baseReward = remember { noteEvent.getReward() }
|
||||||
if (baseReward != null) {
|
if (baseReward != null) {
|
||||||
DisplayReward(baseReward, note, account, nav)
|
DisplayReward(baseReward, note, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
val pow = remember { noteEvent.getPoWRank() }
|
val pow = remember { noteEvent.getPoWRank() }
|
||||||
@@ -1277,21 +1268,19 @@ private fun SecondUserInfoRow(
|
|||||||
private fun FirstUserInfoRow(
|
private fun FirstUserInfoRow(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
showAuthorPicture: Boolean,
|
showAuthorPicture: Boolean,
|
||||||
account: Account,
|
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
var moreActionsExpanded by remember { mutableStateOf(false) }
|
var moreActionsExpanded by remember { mutableStateOf(false) }
|
||||||
val eventNote = remember { baseNote.event } ?: return
|
val eventNote = remember { baseNote.event } ?: return
|
||||||
val time = remember { baseNote.createdAt() } ?: return
|
val time = remember { baseNote.createdAt() } ?: return
|
||||||
val loggedIn = remember { account.userProfile() }
|
|
||||||
val padding = remember {
|
val padding = remember {
|
||||||
Modifier.padding(horizontal = 5.dp)
|
Modifier.padding(horizontal = 5.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
if (showAuthorPicture) {
|
if (showAuthorPicture) {
|
||||||
NoteAuthorPicture(baseNote, nav, loggedIn, 25.dp)
|
NoteAuthorPicture(baseNote, nav, accountViewModel, 25.dp)
|
||||||
Spacer(padding)
|
Spacer(padding)
|
||||||
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
|
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
|
||||||
} else {
|
} else {
|
||||||
@@ -1305,7 +1294,7 @@ private fun FirstUserInfoRow(
|
|||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
DisplayFollowingHashtagsInPost(eventNote, account, nav)
|
DisplayFollowingHashtagsInPost(eventNote, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeAgo(time)
|
TimeAgo(time)
|
||||||
@@ -1353,17 +1342,17 @@ fun TimeAgo(time: Long) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DrawAuthorImages(baseNote: Note, loggedIn: User, nav: (String) -> Unit) {
|
private fun DrawAuthorImages(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val baseChannel = remember { baseNote.channel() }
|
val baseChannel = remember { baseNote.channel() }
|
||||||
val modifier = remember { Modifier.width(55.dp) }
|
val modifier = remember { Modifier.width(55.dp) }
|
||||||
|
|
||||||
Column(modifier) {
|
Column(modifier) {
|
||||||
// Draws the boosted picture outside the boosted card.
|
// Draws the boosted picture outside the boosted card.
|
||||||
Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) {
|
Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) {
|
||||||
NoteAuthorPicture(baseNote, nav, loggedIn, 55.dp)
|
NoteAuthorPicture(baseNote, nav, accountViewModel, 55.dp)
|
||||||
|
|
||||||
if (baseNote.event is RepostEvent) {
|
if (baseNote.event is RepostEvent) {
|
||||||
RepostNoteAuthorPicture(baseNote, nav, loggedIn)
|
RepostNoteAuthorPicture(baseNote, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseNote.event is ChannelMessageEvent && baseChannel != null) {
|
if (baseNote.event is ChannelMessageEvent && baseChannel != null) {
|
||||||
@@ -1427,8 +1416,8 @@ private fun ChannelNotePicture(baseChannel: Channel) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun RepostNoteAuthorPicture(
|
private fun RepostNoteAuthorPicture(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
nav: (String) -> Unit,
|
accountViewModel: AccountViewModel,
|
||||||
loggedIn: User
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val baseRepost = remember { baseNote.replyTo?.lastOrNull() }
|
val baseRepost = remember { baseNote.replyTo?.lastOrNull() }
|
||||||
|
|
||||||
@@ -1441,10 +1430,10 @@ private fun RepostNoteAuthorPicture(
|
|||||||
baseRepost?.let {
|
baseRepost?.let {
|
||||||
Box(modifier) {
|
Box(modifier) {
|
||||||
NoteAuthorPicture(
|
NoteAuthorPicture(
|
||||||
it,
|
baseNote = it,
|
||||||
nav,
|
nav = nav,
|
||||||
loggedIn,
|
accountViewModel = accountViewModel,
|
||||||
35.dp,
|
size = 35.dp,
|
||||||
pictureModifier = Modifier.border(
|
pictureModifier = Modifier.border(
|
||||||
2.dp,
|
2.dp,
|
||||||
MaterialTheme.colors.background,
|
MaterialTheme.colors.background,
|
||||||
@@ -1535,23 +1524,31 @@ fun DisplayHighlight(
|
|||||||
@Composable
|
@Composable
|
||||||
fun DisplayFollowingHashtagsInPost(
|
fun DisplayFollowingHashtagsInPost(
|
||||||
noteEvent: EventInterface,
|
noteEvent: EventInterface,
|
||||||
account: Account,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val followingTags = remember(accountState) {
|
||||||
|
accountState?.account?.followingTagSet() ?: emptySet()
|
||||||
|
}
|
||||||
|
|
||||||
var firstTag by remember { mutableStateOf<String?>(null) }
|
var firstTag by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = noteEvent.id()) {
|
LaunchedEffect(key1 = noteEvent.id(), followingTags) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet())
|
firstTag = noteEvent.firstIsTaggedHashes(followingTags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column() {
|
Column() {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
firstTag?.let {
|
firstTag?.let {
|
||||||
|
val displayTag = remember(firstTag) { AnnotatedString(" #$firstTag") }
|
||||||
|
val route = remember(firstTag) { "Hashtag/$firstTag" }
|
||||||
|
|
||||||
ClickableText(
|
ClickableText(
|
||||||
text = AnnotatedString(" #$firstTag"),
|
text = displayTag,
|
||||||
onClick = { nav("Hashtag/$firstTag") },
|
onClick = { nav(route) },
|
||||||
style = LocalTextStyle.current.copy(
|
style = LocalTextStyle.current.copy(
|
||||||
color = MaterialTheme.colors.primary.copy(
|
color = MaterialTheme.colors.primary.copy(
|
||||||
alpha = 0.52f
|
alpha = 0.52f
|
||||||
@@ -1608,7 +1605,7 @@ fun DisplayPoW(
|
|||||||
fun DisplayReward(
|
fun DisplayReward(
|
||||||
baseReward: BigDecimal,
|
baseReward: BigDecimal,
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
account: Account,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
@@ -1628,10 +1625,46 @@ fun DisplayReward(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val repliesState by baseNote.live().replies.observeAsState()
|
RenderPledgeAmount(baseNote, baseReward, accountViewModel)
|
||||||
val replyNote = repliesState?.note
|
}
|
||||||
|
|
||||||
if (replyNote?.hasPledgeBy(account.userProfile()) == true) {
|
if (popupExpanded) {
|
||||||
|
AddBountyAmountDialog(baseNote, accountViewModel) {
|
||||||
|
popupExpanded = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RenderPledgeAmount(
|
||||||
|
baseNote: Note,
|
||||||
|
baseReward: BigDecimal,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
val repliesState by baseNote.live().replies.observeAsState()
|
||||||
|
var rewardAmount by remember {
|
||||||
|
mutableStateOf<BigDecimal?>(
|
||||||
|
baseReward
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasPledge by remember {
|
||||||
|
mutableStateOf<Boolean>(
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = repliesState) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
repliesState?.note?.pledgedAmountByOthers()?.let {
|
||||||
|
rewardAmount = baseReward.add(it)
|
||||||
|
}
|
||||||
|
hasPledge = repliesState?.note?.hasPledgeBy(accountViewModel.userProfile()) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasPledge) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Bolt,
|
imageVector = Icons.Default.Bolt,
|
||||||
contentDescription = stringResource(R.string.zaps),
|
contentDescription = stringResource(R.string.zaps),
|
||||||
@@ -1647,34 +1680,12 @@ fun DisplayReward(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rewardAmount by remember {
|
|
||||||
mutableStateOf<BigDecimal?>(
|
|
||||||
baseReward
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = repliesState) {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
replyNote?.pledgedAmountByOthers()?.let {
|
|
||||||
rewardAmount = baseReward.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
showAmount(rewardAmount),
|
showAmount(rewardAmount),
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (popupExpanded) {
|
|
||||||
AddBountyAmountDialog(baseNote, account) {
|
|
||||||
popupExpanded = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BadgeDisplay(baseNote: Note) {
|
fun BadgeDisplay(baseNote: Note) {
|
||||||
val background = MaterialTheme.colors.background
|
val background = MaterialTheme.colors.background
|
||||||
@@ -1868,7 +1879,7 @@ fun FileStorageHeaderDisplay(baseNote: Note) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, loggedIn: User, nav: (String) -> Unit) {
|
fun AudioTrackHeader(noteEvent: AudioTrackEvent, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val media = remember { noteEvent.media() }
|
val media = remember { noteEvent.media() }
|
||||||
val cover = remember { noteEvent.cover() }
|
val cover = remember { noteEvent.cover() }
|
||||||
val subject = remember { noteEvent.subject() }
|
val subject = remember { noteEvent.subject() }
|
||||||
@@ -1908,7 +1919,7 @@ fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, loggedIn: User, nav
|
|||||||
nav("User/${it.second.pubkeyHex}")
|
nav("User/${it.second.pubkeyHex}")
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
UserPicture(it.second, loggedIn, 25.dp)
|
UserPicture(it.second, 25.dp, accountViewModel)
|
||||||
Spacer(Modifier.width(5.dp))
|
Spacer(Modifier.width(5.dp))
|
||||||
UsernameDisplay(it.second, Modifier.weight(1f))
|
UsernameDisplay(it.second, Modifier.weight(1f))
|
||||||
Spacer(Modifier.width(5.dp))
|
Spacer(Modifier.width(5.dp))
|
||||||
@@ -1945,7 +1956,11 @@ fun AudioTrackHeader(noteEvent: AudioTrackEvent, note: Note, loggedIn: User, nav
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LongFormHeader(noteEvent: LongTextNoteEvent, note: Note, loggedIn: User) {
|
private fun LongFormHeader(noteEvent: LongTextNoteEvent, note: Note, accountViewModel: AccountViewModel) {
|
||||||
|
val image = remember(noteEvent) { noteEvent.image() }
|
||||||
|
val title = remember(noteEvent) { noteEvent.title() }
|
||||||
|
val summary = remember(noteEvent) { noteEvent.summary() ?: noteEvent.content.take(200).ifBlank { null } }
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(shape = RoundedCornerShape(15.dp))
|
.clip(shape = RoundedCornerShape(15.dp))
|
||||||
@@ -1956,7 +1971,7 @@ private fun LongFormHeader(noteEvent: LongTextNoteEvent, note: Note, loggedIn: U
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
noteEvent.image()?.let {
|
image?.let {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = it,
|
model = it,
|
||||||
contentDescription = stringResource(
|
contentDescription = stringResource(
|
||||||
@@ -1966,8 +1981,43 @@ private fun LongFormHeader(noteEvent: LongTextNoteEvent, note: Note, loggedIn: U
|
|||||||
contentScale = ContentScale.FillWidth,
|
contentScale = ContentScale.FillWidth,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
} ?: Box() {
|
} ?: CreateImageHeader(note, accountViewModel)
|
||||||
note.author?.info?.banner?.let {
|
|
||||||
|
title?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.body1,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 10.dp, end = 10.dp, top = 10.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
summary?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
|
||||||
|
color = Color.Gray,
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CreateImageHeader(
|
||||||
|
note: Note,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
val banner = remember(note.author?.info) { note.author?.info?.banner }
|
||||||
|
|
||||||
|
Box() {
|
||||||
|
banner?.let {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = it,
|
model = it,
|
||||||
contentDescription = stringResource(
|
contentDescription = stringResource(
|
||||||
@@ -1993,42 +2043,7 @@ private fun LongFormHeader(noteEvent: LongTextNoteEvent, note: Note, loggedIn: U
|
|||||||
.padding(10.dp)
|
.padding(10.dp)
|
||||||
.align(Alignment.BottomStart)
|
.align(Alignment.BottomStart)
|
||||||
) {
|
) {
|
||||||
NoteAuthorPicture(baseNote = note, baseUserAccount = loggedIn, size = 55.dp)
|
NoteAuthorPicture(baseNote = note, accountViewModel = accountViewModel, size = 55.dp)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
noteEvent.title()?.let {
|
|
||||||
Text(
|
|
||||||
text = it,
|
|
||||||
style = MaterialTheme.typography.body1,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(start = 10.dp, end = 10.dp, top = 10.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
noteEvent.summary()?.let {
|
|
||||||
Text(
|
|
||||||
text = it,
|
|
||||||
style = MaterialTheme.typography.caption,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
|
|
||||||
color = Color.Gray,
|
|
||||||
maxLines = 3,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
?: Text(
|
|
||||||
text = noteEvent.content.take(200),
|
|
||||||
style = MaterialTheme.typography.caption,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),
|
|
||||||
color = Color.Gray,
|
|
||||||
maxLines = 3,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2120,11 +2135,11 @@ private fun ShowMoreRelaysButton(onClick: () -> Unit) {
|
|||||||
fun NoteAuthorPicture(
|
fun NoteAuthorPicture(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
userAccount: User,
|
accountViewModel: AccountViewModel,
|
||||||
size: Dp,
|
size: Dp,
|
||||||
pictureModifier: Modifier = Modifier
|
pictureModifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
NoteAuthorPicture(baseNote, userAccount, size, pictureModifier) {
|
NoteAuthorPicture(baseNote, size, accountViewModel, pictureModifier) {
|
||||||
nav("User/${it.pubkeyHex}")
|
nav("User/${it.pubkeyHex}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2132,8 +2147,8 @@ fun NoteAuthorPicture(
|
|||||||
@Composable
|
@Composable
|
||||||
fun NoteAuthorPicture(
|
fun NoteAuthorPicture(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
baseUserAccount: User,
|
|
||||||
size: Dp,
|
size: Dp,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: ((User) -> Unit)? = null
|
onClick: ((User) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
@@ -2164,7 +2179,7 @@ fun NoteAuthorPicture(
|
|||||||
modifier = nullModifier.background(MaterialTheme.colors.background)
|
modifier = nullModifier.background(MaterialTheme.colors.background)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
UserPicture(author, baseUserAccount, size, modifier, onClick)
|
UserPicture(author, size, accountViewModel, modifier, onClick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2173,11 +2188,11 @@ fun NoteAuthorPicture(
|
|||||||
fun UserPicture(
|
fun UserPicture(
|
||||||
user: User,
|
user: User,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
userAccount: User,
|
accountViewModel: AccountViewModel,
|
||||||
size: Dp,
|
size: Dp,
|
||||||
pictureModifier: Modifier = Modifier
|
pictureModifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
UserPicture(user, userAccount, size, pictureModifier) {
|
UserPicture(user, size, accountViewModel, pictureModifier) {
|
||||||
nav("User/${it.pubkeyHex}")
|
nav("User/${it.pubkeyHex}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2186,14 +2201,13 @@ fun UserPicture(
|
|||||||
@Composable
|
@Composable
|
||||||
fun UserPicture(
|
fun UserPicture(
|
||||||
baseUser: User,
|
baseUser: User,
|
||||||
baseUserAccount: User,
|
|
||||||
size: Dp,
|
size: Dp,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
modifier: Modifier = remember { Modifier },
|
modifier: Modifier = remember { Modifier },
|
||||||
onClick: ((User) -> Unit)? = null,
|
onClick: ((User) -> Unit)? = null,
|
||||||
onLongClick: ((User) -> Unit)? = null
|
onLongClick: ((User) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val userState by baseUser.live().metadata.observeAsState()
|
val userState by baseUser.live().metadata.observeAsState()
|
||||||
val accountState by baseUserAccount.live().follows.observeAsState()
|
|
||||||
|
|
||||||
val userPubkey = remember {
|
val userPubkey = remember {
|
||||||
baseUser.pubkeyHex
|
baseUser.pubkeyHex
|
||||||
@@ -2205,12 +2219,6 @@ fun UserPicture(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val showFollowingMark by remember(accountState) {
|
|
||||||
derivedStateOf {
|
|
||||||
accountState?.user?.isFollowingCached(baseUser) == true || baseUser.pubkeyHex == accountState?.user?.pubkeyHex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BaseUser is the same reference as accountState.user
|
// BaseUser is the same reference as accountState.user
|
||||||
val myModifier = remember {
|
val myModifier = remember {
|
||||||
if (onClick != null && onLongClick != null) {
|
if (onClick != null && onLongClick != null) {
|
||||||
@@ -2229,9 +2237,9 @@ fun UserPicture(
|
|||||||
UserPicture(
|
UserPicture(
|
||||||
userHex = userPubkey,
|
userHex = userPubkey,
|
||||||
userPicture = userProfile,
|
userPicture = userProfile,
|
||||||
showFollowingMark = showFollowingMark,
|
|
||||||
size = size,
|
size = size,
|
||||||
modifier = modifier
|
modifier = modifier,
|
||||||
|
accountViewModel = accountViewModel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2240,9 +2248,9 @@ fun UserPicture(
|
|||||||
fun UserPicture(
|
fun UserPicture(
|
||||||
userHex: String,
|
userHex: String,
|
||||||
userPicture: String?,
|
userPicture: String?,
|
||||||
showFollowingMark: Boolean,
|
|
||||||
size: Dp,
|
size: Dp,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val myBoxModifier = remember {
|
val myBoxModifier = remember {
|
||||||
Modifier
|
Modifier
|
||||||
@@ -2257,36 +2265,42 @@ fun UserPicture(
|
|||||||
.clip(shape = CircleShape)
|
.clip(shape = CircleShape)
|
||||||
}
|
}
|
||||||
|
|
||||||
val myResizeImage = remember(userPicture) {
|
Box(myBoxModifier, contentAlignment = TopEnd) {
|
||||||
ResizeImage(userPicture, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(myBoxModifier) {
|
|
||||||
RobohashAsyncImageProxy(
|
RobohashAsyncImageProxy(
|
||||||
robot = userHex,
|
robot = userHex,
|
||||||
model = myResizeImage,
|
model = remember(userPicture) {
|
||||||
|
ResizeImage(userPicture, size)
|
||||||
|
},
|
||||||
contentDescription = stringResource(id = R.string.profile_image),
|
contentDescription = stringResource(id = R.string.profile_image),
|
||||||
modifier = myImageModifier.background(MaterialTheme.colors.background)
|
modifier = myImageModifier.background(MaterialTheme.colors.background)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (showFollowingMark) {
|
ObserveAndDisplayFollowingMark(userHex, size.div(3.5f), accountViewModel)
|
||||||
val myIconBoxModifier = remember {
|
|
||||||
Modifier
|
|
||||||
.width(size.div(3.5f))
|
|
||||||
.height(size.div(3.5f))
|
|
||||||
.align(Alignment.TopEnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
FollowingIcon(myIconBoxModifier, size)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun FollowingIcon(
|
private fun ObserveAndDisplayFollowingMark(userHex: String, iconSize: Dp, accountViewModel: AccountViewModel) {
|
||||||
myIconBoxModifier: Modifier,
|
val accountFollowsState by accountViewModel.account.userProfile().live().follows.observeAsState()
|
||||||
size: Dp
|
|
||||||
) {
|
val showFollowingMark by remember(accountFollowsState) {
|
||||||
|
derivedStateOf {
|
||||||
|
accountFollowsState?.user?.isFollowingCached(userHex) == true ||
|
||||||
|
(userHex == accountViewModel.account.userProfile().pubkeyHex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showFollowingMark) {
|
||||||
|
FollowingIcon(iconSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FollowingIcon(iconSize: Dp) {
|
||||||
|
val myIconBoxModifier = remember {
|
||||||
|
Modifier.size(iconSize)
|
||||||
|
}
|
||||||
|
|
||||||
Box(myIconBoxModifier, contentAlignment = Alignment.Center) {
|
Box(myIconBoxModifier, contentAlignment = Alignment.Center) {
|
||||||
val myIconBackgroundModifier = remember {
|
val myIconBackgroundModifier = remember {
|
||||||
Modifier
|
Modifier
|
||||||
@@ -2299,9 +2313,7 @@ private fun FollowingIcon(
|
|||||||
)
|
)
|
||||||
|
|
||||||
val myIconModifier = remember {
|
val myIconModifier = remember {
|
||||||
Modifier
|
Modifier.size(iconSize)
|
||||||
.width(size.div(3.5f))
|
|
||||||
.height(size.div(3.5f))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
@@ -18,11 +18,17 @@ import com.google.accompanist.flowlayout.FlowRow
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.*
|
import com.vitorpamplona.amethyst.model.*
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReplyInformation(replyTo: List<Note>?, mentions: List<String>, account: Account, nav: (String) -> Unit) {
|
fun ReplyInformation(
|
||||||
|
replyTo: List<Note>?,
|
||||||
|
mentions: List<String>,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
var dupMentions by remember { mutableStateOf<List<User>?>(null) }
|
var dupMentions by remember { mutableStateOf<List<User>?>(null) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
@@ -32,15 +38,21 @@ fun ReplyInformation(replyTo: List<Note>?, mentions: List<String>, account: Acco
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dupMentions != null) {
|
if (dupMentions != null) {
|
||||||
ReplyInformation(replyTo, dupMentions, account) {
|
ReplyInformation(replyTo, dupMentions, accountViewModel) {
|
||||||
nav("User/${it.pubkeyHex}")
|
nav("User/${it.pubkeyHex}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReplyInformation(replyTo: List<Note>?, dupMentions: List<User>?, account: Account, prefix: String = "", onUserTagClick: (User) -> Unit) {
|
fun ReplyInformation(
|
||||||
val mentions = dupMentions?.toSet()?.sortedBy { !account.userProfile().isFollowingCached(it) }
|
replyTo: List<Note>?,
|
||||||
|
dupMentions: List<User>?,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
prefix: String = "",
|
||||||
|
onUserTagClick: (User) -> Unit
|
||||||
|
) {
|
||||||
|
val mentions = dupMentions?.toSet()?.sortedBy { !accountViewModel.account.userProfile().isFollowingCached(it) }
|
||||||
var expanded by remember { mutableStateOf((mentions?.size ?: 0) <= 2) }
|
var expanded by remember { mutableStateOf((mentions?.size ?: 0) <= 2) }
|
||||||
|
|
||||||
FlowRow() {
|
FlowRow() {
|
||||||
@@ -66,7 +78,7 @@ fun ReplyInformation(replyTo: List<Note>?, dupMentions: List<User>?, account: Ac
|
|||||||
)
|
)
|
||||||
} else if (idx < repliesToDisplay.size - 1) {
|
} else if (idx < repliesToDisplay.size - 1) {
|
||||||
Text(
|
Text(
|
||||||
"${stringResource(R.string.and)}",
|
stringResource(R.string.and),
|
||||||
fontSize = 13.sp,
|
fontSize = 13.sp,
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
)
|
)
|
||||||
@@ -80,7 +92,7 @@ fun ReplyInformation(replyTo: List<Note>?, dupMentions: List<User>?, account: Ac
|
|||||||
)
|
)
|
||||||
} else if (idx < repliesToDisplay.size) {
|
} else if (idx < repliesToDisplay.size) {
|
||||||
Text(
|
Text(
|
||||||
"${stringResource(R.string.and)}",
|
stringResource(R.string.and),
|
||||||
fontSize = 13.sp,
|
fontSize = 13.sp,
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
)
|
)
|
||||||
@@ -105,7 +117,13 @@ fun ReplyInformation(replyTo: List<Note>?, dupMentions: List<User>?, account: Ac
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReplyInformationChannel(replyTo: List<Note>?, mentions: List<String>, channel: Channel, account: Account, nav: (String) -> Unit) {
|
fun ReplyInformationChannel(
|
||||||
|
replyTo: List<Note>?,
|
||||||
|
mentions: List<String>,
|
||||||
|
channel: Channel,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
var sortedMentions by remember { mutableStateOf<List<User>?>(null) }
|
var sortedMentions by remember { mutableStateOf<List<User>?>(null) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
@@ -113,7 +131,7 @@ fun ReplyInformationChannel(replyTo: List<Note>?, mentions: List<String>, channe
|
|||||||
sortedMentions = mentions
|
sortedMentions = mentions
|
||||||
.mapNotNull { LocalCache.checkGetOrCreateUser(it) }
|
.mapNotNull { LocalCache.checkGetOrCreateUser(it) }
|
||||||
.toSet()
|
.toSet()
|
||||||
.sortedBy { account.isFollowing(it) }
|
.sortedBy { accountViewModel.account.isFollowing(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,11 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.material.Divider
|
import androidx.compose.material.Divider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.FollowButton
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShowUserButton
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UnfollowButton
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UserCompose(
|
fun UserCompose(
|
||||||
@@ -32,14 +25,6 @@ fun UserCompose(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = accountState?.account ?: return
|
|
||||||
|
|
||||||
val userState by account.userProfile().live().follows.observeAsState()
|
|
||||||
val userFollows = userState?.user ?: return
|
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.clickable(
|
Modifier.clickable(
|
||||||
@@ -50,7 +35,7 @@ fun UserCompose(
|
|||||||
modifier = overallModifier,
|
modifier = overallModifier,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
UserPicture(baseUser, nav, account.userProfile(), 55.dp)
|
UserPicture(baseUser, nav, accountViewModel, 55.dp)
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
|
Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
@@ -61,15 +46,7 @@ fun UserCompose(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||||
if (account.isHidden(baseUser)) {
|
UserActionOptions(baseUser, accountViewModel)
|
||||||
ShowUserButton {
|
|
||||||
account.showUser(baseUser.pubkeyHex)
|
|
||||||
}
|
|
||||||
} else if (userFollows.isFollowingCached(baseUser)) {
|
|
||||||
UnfollowButton { coroutineScope.launch(Dispatchers.IO) { account.unfollow(baseUser) } }
|
|
||||||
} else {
|
|
||||||
FollowButton({ coroutineScope.launch(Dispatchers.IO) { account.follow(baseUser) } })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,41 +19,32 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.FollowButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.FollowButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShowUserButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShowUserButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UnfollowButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UnfollowButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.math.BigDecimal
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = accountState?.account ?: return
|
|
||||||
|
|
||||||
val userState by account.userProfile().live().follows.observeAsState()
|
|
||||||
val userFollows = userState?.user ?: return
|
|
||||||
|
|
||||||
val noteState by baseNote.second.live().metadata.observeAsState()
|
|
||||||
val noteZap = noteState?.note ?: return
|
|
||||||
|
|
||||||
val baseNoteRequest by baseNote.first.live().metadata.observeAsState()
|
val baseNoteRequest by baseNote.first.live().metadata.observeAsState()
|
||||||
val noteZapRequest = baseNoteRequest?.note ?: return
|
val noteZapRequest = remember(baseNoteRequest) { baseNoteRequest?.note } ?: return
|
||||||
|
|
||||||
val baseAuthor = noteZapRequest.author
|
var baseAuthor by remember {
|
||||||
|
mutableStateOf(noteZapRequest.author)
|
||||||
val coroutineScope = rememberCoroutineScope()
|
}
|
||||||
|
|
||||||
if (baseAuthor == null) {
|
if (baseAuthor == null) {
|
||||||
BlankNote()
|
BlankNote()
|
||||||
@@ -61,9 +52,39 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
|
|||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.clickable(
|
Modifier.clickable(
|
||||||
onClick = { nav("User/${baseAuthor.pubkeyHex}") }
|
onClick = { nav("User/${baseAuthor?.pubkeyHex}") }
|
||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
launch(Dispatchers.Default) {
|
||||||
|
(noteZapRequest.event as? LnZapRequestEvent)?.let {
|
||||||
|
val decryptedContent = accountViewModel.decryptZap(noteZapRequest)
|
||||||
|
if (decryptedContent != null) {
|
||||||
|
baseAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
baseAuthor?.let {
|
||||||
|
RenderZapNote(it, baseNote.second, nav, accountViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider(
|
||||||
|
modifier = Modifier.padding(top = 10.dp),
|
||||||
|
thickness = 0.25.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RenderZapNote(
|
||||||
|
baseAuthor: User,
|
||||||
|
zapNote: Note,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -74,9 +95,13 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
|
|||||||
),
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
UserPicture(baseAuthor, nav, account.userProfile(), 55.dp)
|
UserPicture(baseAuthor, nav, accountViewModel, 55.dp)
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(start = 10.dp).weight(1f)) {
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 10.dp)
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
UsernameDisplay(baseAuthor)
|
UsernameDisplay(baseAuthor)
|
||||||
}
|
}
|
||||||
@@ -84,44 +109,63 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
|
|||||||
AboutDisplay(baseAuthor)
|
AboutDisplay(baseAuthor)
|
||||||
}
|
}
|
||||||
|
|
||||||
var zapAmount by remember { mutableStateOf<BigDecimal?>(null) }
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = noteZap) {
|
|
||||||
launch(Dispatchers.IO) {
|
|
||||||
zapAmount = (noteZap.event as? LnZapEvent)?.amount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(start = 10.dp),
|
modifier = Modifier.padding(start = 10.dp),
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
|
ZapAmount(zapNote)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||||
|
UserActionOptions(baseAuthor, accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ZapAmount(zapEventNote: Note) {
|
||||||
|
val noteState by zapEventNote.live().metadata.observeAsState()
|
||||||
|
val noteZap = remember(noteState) { noteState?.note } ?: return
|
||||||
|
|
||||||
|
var zapAmount by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = noteZap) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
zapAmount = showAmountAxis((noteZap.event as? LnZapEvent)?.amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zapAmount?.let {
|
||||||
Text(
|
Text(
|
||||||
"${showAmount(zapAmount)} ${stringResource(R.string.sats)}",
|
text = it,
|
||||||
color = BitcoinOrange,
|
color = BitcoinOrange,
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
fontWeight = FontWeight.W500
|
fontWeight = FontWeight.W500
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
@Composable
|
||||||
if (account.isHidden(baseAuthor)) {
|
fun UserActionOptions(
|
||||||
|
baseAuthor: User,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val isHidden = remember(accountState) { accountState?.account?.isHidden(baseAuthor) } ?: return
|
||||||
|
|
||||||
|
val userState by accountViewModel.account.userProfile().live().follows.observeAsState()
|
||||||
|
val isFollowing = remember(userState) { userState?.user?.isFollowingCached(baseAuthor) } ?: return
|
||||||
|
|
||||||
|
if (isHidden) {
|
||||||
ShowUserButton {
|
ShowUserButton {
|
||||||
account.showUser(baseAuthor.pubkeyHex)
|
accountViewModel.show(baseAuthor)
|
||||||
}
|
}
|
||||||
} else if (userFollows.isFollowingCached(baseAuthor)) {
|
} else if (isFollowing) {
|
||||||
UnfollowButton { coroutineScope.launch(Dispatchers.IO) { account.unfollow(baseAuthor) } }
|
UnfollowButton { coroutineScope.launch(Dispatchers.IO) { accountViewModel.unfollow(baseAuthor) } }
|
||||||
} else {
|
} else {
|
||||||
FollowButton({ coroutineScope.launch(Dispatchers.IO) { account.follow(baseAuthor) } })
|
FollowButton({ coroutineScope.launch(Dispatchers.IO) { accountViewModel.follow(baseAuthor) } })
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Divider(
|
|
||||||
modifier = Modifier.padding(top = 10.dp),
|
|
||||||
thickness = 0.25.dp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,10 +43,6 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor
|
|||||||
val noteState by zapSetCard.note.live().metadata.observeAsState()
|
val noteState by zapSetCard.note.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = accountState?.account ?: return
|
|
||||||
|
|
||||||
val noteEvent = note?.event
|
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
@@ -77,7 +73,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor
|
|||||||
scope.launch {
|
scope.launch {
|
||||||
routeFor(
|
routeFor(
|
||||||
note,
|
note,
|
||||||
account.userProfile()
|
accountViewModel.userProfile()
|
||||||
)?.let { nav(it) }
|
)?.let { nav(it) }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -116,7 +112,7 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeFor
|
|||||||
NoteAuthorPicture(
|
NoteAuthorPicture(
|
||||||
baseNote = it.key,
|
baseNote = it.key,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
userAccount = account.userProfile(),
|
accountViewModel = accountViewModel,
|
||||||
size = 35.dp
|
size = 35.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -16,7 +15,6 @@ import androidx.compose.material.icons.filled.Bolt
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -35,12 +33,8 @@ import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = accountState?.account ?: return
|
|
||||||
|
|
||||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = zapSetCard.createdAt()) {
|
LaunchedEffect(key1 = zapSetCard.createdAt()) {
|
||||||
@@ -96,7 +90,7 @@ fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false,
|
|||||||
NoteAuthorPicture(
|
NoteAuthorPicture(
|
||||||
baseNote = it.key,
|
baseNote = it.key,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
userAccount = account.userProfile(),
|
accountViewModel = accountViewModel,
|
||||||
size = 35.dp
|
size = 35.dp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ fun NoteMaster(
|
|||||||
} else if (!account.isAcceptable(noteForReports) && !showHiddenNote) {
|
} else if (!account.isAcceptable(noteForReports) && !showHiddenNote) {
|
||||||
HiddenNote(
|
HiddenNote(
|
||||||
account.getRelevantReports(noteForReports),
|
account.getRelevantReports(noteForReports),
|
||||||
account.userProfile(),
|
accountViewModel,
|
||||||
Modifier,
|
Modifier,
|
||||||
false,
|
false,
|
||||||
nav,
|
nav,
|
||||||
@@ -255,7 +255,7 @@ fun NoteMaster(
|
|||||||
NoteAuthorPicture(
|
NoteAuthorPicture(
|
||||||
baseNote = baseNote,
|
baseNote = baseNote,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
userAccount = account.userProfile(),
|
accountViewModel = accountViewModel,
|
||||||
size = 55.dp
|
size = 55.dp
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ fun NoteMaster(
|
|||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
|
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
|
||||||
|
|
||||||
DisplayFollowingHashtagsInPost(noteEvent, account, nav)
|
DisplayFollowingHashtagsInPost(noteEvent, accountViewModel, nav)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
timeAgo(note.createdAt(), context = context),
|
timeAgo(note.createdAt(), context = context),
|
||||||
@@ -291,7 +291,7 @@ fun NoteMaster(
|
|||||||
|
|
||||||
val baseReward = noteEvent.getReward()
|
val baseReward = noteEvent.getReward()
|
||||||
if (baseReward != null) {
|
if (baseReward != null) {
|
||||||
DisplayReward(baseReward, baseNote, account, nav)
|
DisplayReward(baseReward, baseNote, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
val pow = noteEvent.getPoWRank()
|
val pow = noteEvent.getPoWRank()
|
||||||
@@ -357,7 +357,7 @@ fun NoteMaster(
|
|||||||
if (noteEvent is PeopleListEvent) {
|
if (noteEvent is PeopleListEvent) {
|
||||||
DisplayPeopleList(baseNote, MaterialTheme.colors.background, accountViewModel, nav)
|
DisplayPeopleList(baseNote, MaterialTheme.colors.background, accountViewModel, nav)
|
||||||
} else if (noteEvent is AudioTrackEvent) {
|
} else if (noteEvent is AudioTrackEvent) {
|
||||||
AudioTrackHeader(noteEvent, note, account.userProfile(), nav)
|
AudioTrackHeader(noteEvent, accountViewModel, nav)
|
||||||
} else if (noteEvent is PinListEvent) {
|
} else if (noteEvent is PinListEvent) {
|
||||||
PinListHeader(baseNote, MaterialTheme.colors.background, accountViewModel, nav)
|
PinListHeader(baseNote, MaterialTheme.colors.background, accountViewModel, nav)
|
||||||
} else if (noteEvent is HighlightEvent) {
|
} else if (noteEvent is HighlightEvent) {
|
||||||
|
|||||||
@@ -26,14 +26,12 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun UserFeedView(
|
fun RefreshingFeedUserFeedView(
|
||||||
viewModel: UserFeedViewModel,
|
viewModel: UserFeedViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
enablePullRefresh: Boolean = true
|
enablePullRefresh: Boolean = true
|
||||||
) {
|
) {
|
||||||
val feedState by viewModel.feedContent.collectAsState()
|
|
||||||
|
|
||||||
var refreshing by remember { mutableStateOf(false) }
|
var refreshing by remember { mutableStateOf(false) }
|
||||||
val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false }
|
val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false }
|
||||||
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
||||||
@@ -46,27 +44,7 @@ fun UserFeedView(
|
|||||||
|
|
||||||
Box(modifier) {
|
Box(modifier) {
|
||||||
Column() {
|
Column() {
|
||||||
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
UserFeedView(viewModel, accountViewModel, nav)
|
||||||
when (state) {
|
|
||||||
is UserFeedState.Empty -> {
|
|
||||||
FeedEmpty {
|
|
||||||
refreshing = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is UserFeedState.FeedError -> {
|
|
||||||
FeedError(state.errorMessage) {
|
|
||||||
refreshing = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is UserFeedState.Loaded -> {
|
|
||||||
refreshing = false
|
|
||||||
FeedLoaded(state, accountViewModel, nav)
|
|
||||||
}
|
|
||||||
is UserFeedState.Loading -> {
|
|
||||||
LoadingFeed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enablePullRefresh) {
|
if (enablePullRefresh) {
|
||||||
@@ -75,6 +53,39 @@ fun UserFeedView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UserFeedView(
|
||||||
|
viewModel: UserFeedViewModel,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val feedState by viewModel.feedContent.collectAsState()
|
||||||
|
|
||||||
|
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
||||||
|
when (state) {
|
||||||
|
is UserFeedState.Empty -> {
|
||||||
|
FeedEmpty {
|
||||||
|
viewModel.invalidateData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is UserFeedState.FeedError -> {
|
||||||
|
FeedError(state.errorMessage) {
|
||||||
|
viewModel.invalidateData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is UserFeedState.Loaded -> {
|
||||||
|
FeedLoaded(state, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
|
is UserFeedState.Loading -> {
|
||||||
|
LoadingFeed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun FeedLoaded(
|
private fun FeedLoaded(
|
||||||
state: UserFeedState.Loaded,
|
state: UserFeedState.Loaded,
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
||||||
@@ -18,9 +20,29 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class NostrUserProfileFollowsUserFeedViewModel : UserFeedViewModel(UserProfileFollowsFeedFilter)
|
class NostrUserProfileFollowsUserFeedViewModel(val user: User, val account: Account) : UserFeedViewModel(UserProfileFollowsFeedFilter(user, account)) {
|
||||||
class NostrUserProfileFollowersUserFeedViewModel : UserFeedViewModel(UserProfileFollowersFeedFilter)
|
class Factory(val user: User, val account: Account) : ViewModelProvider.Factory {
|
||||||
class NostrHiddenAccountsFeedViewModel : UserFeedViewModel(HiddenAccountsFeedFilter)
|
override fun <NostrUserProfileFollowsUserFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileFollowsUserFeedViewModel>): NostrUserProfileFollowsUserFeedViewModel {
|
||||||
|
return NostrUserProfileFollowsUserFeedViewModel(user, account) as NostrUserProfileFollowsUserFeedViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NostrUserProfileFollowersUserFeedViewModel(val user: User, val account: Account) : UserFeedViewModel(UserProfileFollowersFeedFilter(user, account)) {
|
||||||
|
class Factory(val user: User, val account: Account) : ViewModelProvider.Factory {
|
||||||
|
override fun <NostrUserProfileFollowersUserFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileFollowersUserFeedViewModel>): NostrUserProfileFollowersUserFeedViewModel {
|
||||||
|
return NostrUserProfileFollowersUserFeedViewModel(user, account) as NostrUserProfileFollowersUserFeedViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NostrHiddenAccountsFeedViewModel(val account: Account) : UserFeedViewModel(HiddenAccountsFeedFilter(account)) {
|
||||||
|
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||||
|
override fun <NostrHiddenAccountsFeedViewModel : ViewModel> create(modelClass: Class<NostrHiddenAccountsFeedViewModel>): NostrHiddenAccountsFeedViewModel {
|
||||||
|
return NostrHiddenAccountsFeedViewModel(account) as NostrHiddenAccountsFeedViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open class UserFeedViewModel(val dataSource: FeedFilter<User>) : ViewModel() {
|
open class UserFeedViewModel(val dataSource: FeedFilter<User>) : ViewModel() {
|
||||||
private val _feedContent = MutableStateFlow<UserFeedState>(UserFeedState.Loading)
|
private val _feedContent = MutableStateFlow<UserFeedState>(UserFeedState.Loading)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import androidx.compose.material.icons.filled.Share
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -62,7 +63,6 @@ import androidx.lifecycle.Lifecycle
|
|||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
@@ -145,7 +145,7 @@ fun ChannelScreen(
|
|||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
ChannelHeader(
|
ChannelHeader(
|
||||||
channel,
|
channel,
|
||||||
account,
|
accountViewModel,
|
||||||
nav = nav
|
nav = nav
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@ fun ChannelScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelHeader(channelHex: String, account: Account, nav: (String) -> Unit) {
|
fun ChannelHeader(channelHex: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
var baseChannel by remember { mutableStateOf<Channel?>(null) }
|
var baseChannel by remember { mutableStateOf<Channel?>(null) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
@@ -271,12 +271,12 @@ fun ChannelHeader(channelHex: String, account: Account, nav: (String) -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
baseChannel?.let {
|
baseChannel?.let {
|
||||||
ChannelHeader(it, account, nav)
|
ChannelHeader(it, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelHeader(baseChannel: Channel, account: Account, nav: (String) -> Unit) {
|
fun ChannelHeader(baseChannel: Channel, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val channelState by baseChannel.live.observeAsState()
|
val channelState by baseChannel.live.observeAsState()
|
||||||
val channel = remember(channelState) { channelState?.channel } ?: return
|
val channel = remember(channelState) { channelState?.channel } ?: return
|
||||||
|
|
||||||
@@ -327,17 +327,7 @@ fun ChannelHeader(baseChannel: Channel, account: Account, nav: (String) -> Unit)
|
|||||||
.height(35.dp)
|
.height(35.dp)
|
||||||
.padding(bottom = 3.dp)
|
.padding(bottom = 3.dp)
|
||||||
) {
|
) {
|
||||||
NoteCopyButton(channel)
|
ChannelActionOptions(channel, accountViewModel, nav)
|
||||||
|
|
||||||
if (channel.creator == account.userProfile()) {
|
|
||||||
EditButton(account, channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account.followingChannels.contains(channel.idHex)) {
|
|
||||||
LeaveButton(account, channel, nav)
|
|
||||||
} else {
|
|
||||||
JoinButton(account, channel, nav)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,6 +339,38 @@ fun ChannelHeader(baseChannel: Channel, account: Account, nav: (String) -> Unit)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ChannelActionOptions(
|
||||||
|
channel: Channel,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
NoteCopyButton(channel)
|
||||||
|
|
||||||
|
val isMe by remember(accountViewModel) {
|
||||||
|
derivedStateOf {
|
||||||
|
channel.creator == accountViewModel.account.userProfile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val isFollowing by remember(accountState) {
|
||||||
|
derivedStateOf {
|
||||||
|
accountState?.account?.followingChannels?.contains(channel.idHex) ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMe) {
|
||||||
|
EditButton(accountViewModel, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFollowing) {
|
||||||
|
LeaveButton(accountViewModel, channel, nav)
|
||||||
|
} else {
|
||||||
|
JoinButton(accountViewModel, channel, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun NoteCopyButton(
|
private fun NoteCopyButton(
|
||||||
note: Channel
|
note: Channel
|
||||||
@@ -385,13 +407,13 @@ private fun NoteCopyButton(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun EditButton(account: Account, channel: Channel) {
|
private fun EditButton(accountViewModel: AccountViewModel, channel: Channel) {
|
||||||
var wantsToPost by remember {
|
var wantsToPost by remember {
|
||||||
mutableStateOf(false)
|
mutableStateOf(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wantsToPost) {
|
if (wantsToPost) {
|
||||||
NewChannelView({ wantsToPost = false }, account = account, channel)
|
NewChannelView({ wantsToPost = false }, accountViewModel, channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
@@ -414,11 +436,11 @@ private fun EditButton(account: Account, channel: Channel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun JoinButton(account: Account, channel: Channel, nav: (String) -> Unit) {
|
private fun JoinButton(accountViewModel: AccountViewModel, channel: Channel, nav: (String) -> Unit) {
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(horizontal = 3.dp),
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
account.joinChannel(channel.idHex)
|
accountViewModel.account.joinChannel(channel.idHex)
|
||||||
},
|
},
|
||||||
shape = RoundedCornerShape(20.dp),
|
shape = RoundedCornerShape(20.dp),
|
||||||
colors = ButtonDefaults
|
colors = ButtonDefaults
|
||||||
@@ -432,11 +454,11 @@ private fun JoinButton(account: Account, channel: Channel, nav: (String) -> Unit
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LeaveButton(account: Account, channel: Channel, nav: (String) -> Unit) {
|
private fun LeaveButton(accountViewModel: AccountViewModel, channel: Channel, nav: (String) -> Unit) {
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(horizontal = 3.dp),
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
onClick = {
|
onClick = {
|
||||||
account.leaveChannel(channel.idHex)
|
accountViewModel.account.leaveChannel(channel.idHex)
|
||||||
nav(Route.Message.route)
|
nav(Route.Message.route)
|
||||||
},
|
},
|
||||||
shape = RoundedCornerShape(20.dp),
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -61,18 +60,15 @@ import com.vitorpamplona.amethyst.ui.screen.NostrChatRoomFeedViewModel
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = accountState?.account
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val chatRoomScreenModel: NewPostViewModel = viewModel()
|
val chatRoomScreenModel: NewPostViewModel = viewModel()
|
||||||
|
chatRoomScreenModel.account = accountViewModel.account
|
||||||
|
|
||||||
chatRoomScreenModel.account = account
|
if (userId != null) {
|
||||||
|
|
||||||
if (account != null && userId != null) {
|
|
||||||
val replyTo = remember { mutableStateOf<Note?>(null) }
|
val replyTo = remember { mutableStateOf<Note?>(null) }
|
||||||
|
|
||||||
ChatroomFeedFilter.loadMessagesBetween(account, userId)
|
ChatroomFeedFilter.loadMessagesBetween(accountViewModel.account, userId)
|
||||||
NostrChatroomDataSource.loadMessagesBetween(account, userId)
|
NostrChatroomDataSource.loadMessagesBetween(accountViewModel.account, userId)
|
||||||
|
|
||||||
val feedViewModel: NostrChatRoomFeedViewModel = viewModel()
|
val feedViewModel: NostrChatRoomFeedViewModel = viewModel()
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
@@ -105,7 +101,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, nav: (St
|
|||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
NostrChatroomDataSource.withUser?.let {
|
NostrChatroomDataSource.withUser?.let {
|
||||||
ChatroomHeader(it, account.userProfile(), nav = nav)
|
ChatroomHeader(it, accountViewModel, nav = nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
@@ -178,7 +174,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, nav: (St
|
|||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
PostButton(
|
PostButton(
|
||||||
onPost = {
|
onPost = {
|
||||||
account.sendPrivateMessage(chatRoomScreenModel.message.text, userId, replyTo.value, null, wantsToMarkAsSensitive = false)
|
accountViewModel.account.sendPrivateMessage(chatRoomScreenModel.message.text, userId, replyTo.value, null, wantsToMarkAsSensitive = false)
|
||||||
chatRoomScreenModel.message = TextFieldValue("")
|
chatRoomScreenModel.message = TextFieldValue("")
|
||||||
replyTo.value = null
|
replyTo.value = null
|
||||||
feedViewModel.invalidateData() // Don't wait a full second before updating
|
feedViewModel.invalidateData() // Don't wait a full second before updating
|
||||||
@@ -193,7 +189,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, nav: (St
|
|||||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
modifier = Modifier.padding(start = 5.dp)
|
modifier = Modifier.padding(start = 5.dp)
|
||||||
) {
|
) {
|
||||||
chatRoomScreenModel.upload(it, "", account.defaultFileServer, context)
|
chatRoomScreenModel.upload(it, "", accountViewModel.account.defaultFileServer, context)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TextFieldDefaults.textFieldColors(
|
colors = TextFieldDefaults.textFieldColors(
|
||||||
@@ -207,7 +203,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, nav: (St
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatroomHeader(baseUser: User, accountUser: User, nav: (String) -> Unit) {
|
fun ChatroomHeader(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.clickable(
|
modifier = Modifier.clickable(
|
||||||
onClick = { nav("User/${baseUser.pubkeyHex}") }
|
onClick = { nav("User/${baseUser.pubkeyHex}") }
|
||||||
@@ -217,7 +213,7 @@ fun ChatroomHeader(baseUser: User, accountUser: User, nav: (String) -> Unit) {
|
|||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
UserPicture(
|
UserPicture(
|
||||||
baseUser = baseUser,
|
baseUser = baseUser,
|
||||||
baseUserAccount = accountUser,
|
accountViewModel = accountViewModel,
|
||||||
size = 35.dp
|
size = 35.dp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -22,7 +24,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
|
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHashtagFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHashtagFeedViewModel
|
||||||
@@ -32,17 +33,14 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = accountState?.account ?: return
|
|
||||||
|
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
|
|
||||||
if (tag != null) {
|
if (tag != null) {
|
||||||
HashtagFeedFilter.loadHashtag(account, tag)
|
HashtagFeedFilter.loadHashtag(accountViewModel.account, tag)
|
||||||
val feedViewModel: NostrHashtagFeedViewModel = viewModel()
|
val feedViewModel: NostrHashtagFeedViewModel = viewModel()
|
||||||
|
|
||||||
LaunchedEffect(tag) {
|
LaunchedEffect(tag) {
|
||||||
HashtagFeedFilter.loadHashtag(account, tag)
|
HashtagFeedFilter.loadHashtag(accountViewModel.account, tag)
|
||||||
NostrHashtagDataSource.loadHashtag(tag)
|
NostrHashtagDataSource.loadHashtag(tag)
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.invalidateData()
|
||||||
}
|
}
|
||||||
@@ -51,14 +49,14 @@ fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, nav: (String
|
|||||||
val observer = LifecycleEventObserver { _, event ->
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
if (event == Lifecycle.Event.ON_RESUME) {
|
||||||
println("Hashtag Start")
|
println("Hashtag Start")
|
||||||
HashtagFeedFilter.loadHashtag(account, tag)
|
HashtagFeedFilter.loadHashtag(accountViewModel.account, tag)
|
||||||
NostrHashtagDataSource.loadHashtag(tag)
|
NostrHashtagDataSource.loadHashtag(tag)
|
||||||
NostrHashtagDataSource.start()
|
NostrHashtagDataSource.start()
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.invalidateData()
|
||||||
}
|
}
|
||||||
if (event == Lifecycle.Event.ON_PAUSE) {
|
if (event == Lifecycle.Event.ON_PAUSE) {
|
||||||
println("Hashtag Stop")
|
println("Hashtag Stop")
|
||||||
HashtagFeedFilter.loadHashtag(account, null)
|
HashtagFeedFilter.loadHashtag(accountViewModel.account, null)
|
||||||
NostrHashtagDataSource.loadHashtag(null)
|
NostrHashtagDataSource.loadHashtag(null)
|
||||||
NostrHashtagDataSource.stop()
|
NostrHashtagDataSource.stop()
|
||||||
}
|
}
|
||||||
@@ -74,7 +72,7 @@ fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, nav: (String
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 0.dp)
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
) {
|
) {
|
||||||
HashtagHeader(tag, account)
|
HashtagHeader(tag, accountViewModel)
|
||||||
RefresheableView(feedViewModel, accountViewModel, nav, null)
|
RefresheableView(feedViewModel, accountViewModel, nav, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,12 +80,7 @@ fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, nav: (String
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HashtagHeader(tag: String, account: Account) {
|
fun HashtagHeader(tag: String, account: AccountViewModel) {
|
||||||
val userState by account.userProfile().live().follows.observeAsState()
|
|
||||||
val userFollows = userState?.user ?: return
|
|
||||||
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
Column() {
|
Column() {
|
||||||
Column(modifier = Modifier.padding(12.dp)) {
|
Column(modifier = Modifier.padding(12.dp)) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
@@ -107,11 +100,7 @@ fun HashtagHeader(tag: String, account: Account) {
|
|||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (userFollows.isFollowingHashtagCached(tag)) {
|
HashtagActionOptions(tag, account)
|
||||||
UnfollowButton { coroutineScope.launch(Dispatchers.IO) { account.unfollow(tag) } }
|
|
||||||
} else {
|
|
||||||
FollowButton({ coroutineScope.launch(Dispatchers.IO) { account.follow(tag) } })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,3 +112,24 @@ fun HashtagHeader(tag: String, account: Account) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun HashtagActionOptions(
|
||||||
|
tag: String,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val userState by accountViewModel.userProfile().live().follows.observeAsState()
|
||||||
|
val isFollowingTag by remember(userState) {
|
||||||
|
derivedStateOf {
|
||||||
|
userState?.user?.isFollowingHashtagCached(tag) ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFollowingTag) {
|
||||||
|
UnfollowButton { coroutineScope.launch(Dispatchers.IO) { accountViewModel.account.unfollow(tag) } }
|
||||||
|
} else {
|
||||||
|
FollowButton({ coroutineScope.launch(Dispatchers.IO) { accountViewModel.account.follow(tag) } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+37
-17
@@ -24,33 +24,32 @@ import androidx.lifecycle.Lifecycle
|
|||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HiddenAccountsFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.UserFeedView
|
import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HiddenUsersScreen(accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
|
val feedViewModel: NostrHiddenAccountsFeedViewModel = viewModel(
|
||||||
|
factory = NostrHiddenAccountsFeedViewModel.Factory(accountViewModel.account)
|
||||||
|
)
|
||||||
|
|
||||||
|
HiddenUsersScreen(feedViewModel, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun HiddenUsersScreen(accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
fun HiddenUsersScreen(
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
feedViewModel: NostrHiddenAccountsFeedViewModel,
|
||||||
val account = accountState?.account
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
val lifeCycleOwner = LocalLifecycleOwner.current
|
val lifeCycleOwner = LocalLifecycleOwner.current
|
||||||
val feedViewModel: NostrHiddenAccountsFeedViewModel = viewModel()
|
|
||||||
|
|
||||||
if (account != null) {
|
|
||||||
HiddenAccountsFeedFilter.account = account
|
|
||||||
|
|
||||||
LaunchedEffect(accountViewModel) {
|
|
||||||
HiddenAccountsFeedFilter.account = account
|
|
||||||
feedViewModel.invalidateData()
|
|
||||||
}
|
|
||||||
|
|
||||||
DisposableEffect(accountViewModel) {
|
DisposableEffect(accountViewModel) {
|
||||||
val observer = LifecycleEventObserver { _, event ->
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
if (event == Lifecycle.Event.ON_RESUME) {
|
if (event == Lifecycle.Event.ON_RESUME) {
|
||||||
println("Hidden Users Start")
|
println("Hidden Users Start")
|
||||||
HiddenAccountsFeedFilter.account = account
|
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.invalidateData()
|
||||||
}
|
}
|
||||||
if (event == Lifecycle.Event.ON_PAUSE) {
|
if (event == Lifecycle.Event.ON_PAUSE) {
|
||||||
@@ -83,10 +82,31 @@ fun HiddenUsersScreen(accountViewModel: AccountViewModel, nav: (String) -> Unit)
|
|||||||
}
|
}
|
||||||
HorizontalPager(pageCount = 1, state = pagerState) { page ->
|
HorizontalPager(pageCount = 1, state = pagerState) { page ->
|
||||||
when (page) {
|
when (page) {
|
||||||
0 -> UserFeedView(feedViewModel, accountViewModel, nav)
|
0 -> RefreshingUserFeedView(feedViewModel, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RefreshingUserFeedView(
|
||||||
|
feedViewModel: NostrHiddenAccountsFeedViewModel,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
WatchAccount(feedViewModel, accountViewModel)
|
||||||
|
RefreshingFeedUserFeedView(feedViewModel, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WatchAccount(
|
||||||
|
feedViewModel: NostrHiddenAccountsFeedViewModel,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(accountViewModel, accountState) {
|
||||||
|
feedViewModel.invalidateData()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingButtons(navController, accountViewModel, accountStateViewModel)
|
FloatingButtons(navController, accountViewModel, accountStateViewModel, nav)
|
||||||
},
|
},
|
||||||
scaffoldState = scaffoldState
|
scaffoldState = scaffoldState
|
||||||
) {
|
) {
|
||||||
@@ -161,17 +161,14 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FloatingButtons(navController: NavHostController, accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel) {
|
fun FloatingButtons(
|
||||||
|
navController: NavHostController,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
accountStateViewModel: AccountStateViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
val accountState by accountStateViewModel.accountContent.collectAsState()
|
val accountState by accountStateViewModel.accountContent.collectAsState()
|
||||||
|
|
||||||
val nav = remember {
|
|
||||||
{ route: String ->
|
|
||||||
if (getRouteWithArguments(navController) != route) {
|
|
||||||
navController.navigate(route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Crossfade(targetState = accountState, animationSpec = tween(durationMillis = 100)) { state ->
|
Crossfade(targetState = accountState, animationSpec = tween(durationMillis = 100)) { state ->
|
||||||
when (state) {
|
when (state) {
|
||||||
is AccountState.LoggedInViewOnly -> {
|
is AccountState.LoggedInViewOnly -> {
|
||||||
@@ -185,7 +182,7 @@ fun FloatingButtons(navController: NavHostController, accountViewModel: AccountV
|
|||||||
NewNoteButton(accountViewModel, nav)
|
NewNoteButton(accountViewModel, nav)
|
||||||
}
|
}
|
||||||
if (currentRoute(navController) == Route.Message.base) {
|
if (currentRoute(navController) == Route.Message.base) {
|
||||||
ChannelFabColumn(state.account, nav)
|
ChannelFabColumn(accountViewModel, nav)
|
||||||
}
|
}
|
||||||
if (currentRoute(navController)?.substringBefore("?") == Route.Video.base) {
|
if (currentRoute(navController)?.substringBefore("?") == Route.Video.base) {
|
||||||
NewImageButton(accountViewModel, nav)
|
NewImageButton(accountViewModel, nav)
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
|
|||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus
|
import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus
|
||||||
import com.vitorpamplona.amethyst.ui.components.InvoiceRequest
|
import com.vitorpamplona.amethyst.ui.components.InvoiceRequest
|
||||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
@@ -74,14 +73,11 @@ import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog
|
|||||||
import com.vitorpamplona.amethyst.ui.components.figureOutMimeType
|
import com.vitorpamplona.amethyst.ui.components.figureOutMimeType
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileBookmarksFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileBookmarksFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileConversationsFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileConversationsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileFollowersFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileFollowsFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileZapsFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileZapsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.ShowQRDialog
|
import com.vitorpamplona.amethyst.ui.navigation.ShowQRDialog
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.note.showAmount
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.LnZapFeedView
|
import com.vitorpamplona.amethyst.ui.screen.LnZapFeedView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileBookmarksFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileBookmarksFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileConversationsFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileConversationsFeedViewModel
|
||||||
@@ -91,9 +87,10 @@ import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileNewThreadsFeedViewMo
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileReportFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileReportFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileZapsFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileZapsFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RefresheableView
|
import com.vitorpamplona.amethyst.ui.screen.RefresheableView
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RelayFeedView
|
import com.vitorpamplona.amethyst.ui.screen.RelayFeedView
|
||||||
import com.vitorpamplona.amethyst.ui.screen.RelayFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.RelayFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.UserFeedView
|
import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -108,14 +105,17 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, nav: (Str
|
|||||||
|
|
||||||
var userBase by remember { mutableStateOf<User?>(null) }
|
var userBase by remember { mutableStateOf<User?>(null) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(userId) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
userBase = LocalCache.checkGetOrCreateUser(userId)
|
val newUserBase = LocalCache.checkGetOrCreateUser(userId)
|
||||||
|
if (newUserBase != userBase) {
|
||||||
|
userBase = newUserBase
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userBase?.let {
|
userBase?.let {
|
||||||
ProfileScreen(
|
PrepareViewModels(
|
||||||
baseUser = it,
|
baseUser = it,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav
|
nav = nav
|
||||||
@@ -123,13 +123,44 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, nav: (Str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PrepareViewModels(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
|
val followsFeedViewModel: NostrUserProfileFollowsUserFeedViewModel = viewModel(
|
||||||
|
key = baseUser.pubkeyHex + "UserProfileFollowsUserFeedViewModel",
|
||||||
|
factory = NostrUserProfileFollowsUserFeedViewModel.Factory(
|
||||||
|
baseUser,
|
||||||
|
accountViewModel.account
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val followersFeedViewModel: NostrUserProfileFollowersUserFeedViewModel = viewModel(
|
||||||
|
key = baseUser.pubkeyHex + "UserProfileFollowersUserFeedViewModel",
|
||||||
|
factory = NostrUserProfileFollowersUserFeedViewModel.Factory(
|
||||||
|
baseUser,
|
||||||
|
accountViewModel.account
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
ProfileScreen(
|
||||||
|
baseUser = baseUser,
|
||||||
|
followsFeedViewModel,
|
||||||
|
followersFeedViewModel,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ProfileScreen(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
fun ProfileScreen(
|
||||||
|
baseUser: User,
|
||||||
|
followsFeedViewModel: NostrUserProfileFollowsUserFeedViewModel,
|
||||||
|
followersFeedViewModel: NostrUserProfileFollowersUserFeedViewModel,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
UserProfileNewThreadFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
UserProfileNewThreadFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
||||||
UserProfileConversationsFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
UserProfileConversationsFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
||||||
UserProfileFollowersFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
|
||||||
UserProfileFollowsFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
|
||||||
UserProfileZapsFeedFilter.loadUserProfile(baseUser)
|
UserProfileZapsFeedFilter.loadUserProfile(baseUser)
|
||||||
UserProfileReportsFeedFilter.loadUserProfile(baseUser)
|
UserProfileReportsFeedFilter.loadUserProfile(baseUser)
|
||||||
UserProfileBookmarksFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
UserProfileBookmarksFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
||||||
@@ -245,8 +276,8 @@ fun ProfileScreen(baseUser: User, accountViewModel: AccountViewModel, nav: (Stri
|
|||||||
when (page) {
|
when (page) {
|
||||||
0 -> TabNotesNewThreads(accountViewModel, nav)
|
0 -> TabNotesNewThreads(accountViewModel, nav)
|
||||||
1 -> TabNotesConversations(accountViewModel, nav)
|
1 -> TabNotesConversations(accountViewModel, nav)
|
||||||
2 -> TabFollows(baseUser, accountViewModel, nav)
|
2 -> TabFollows(baseUser, followsFeedViewModel, accountViewModel, nav)
|
||||||
3 -> TabFollowers(baseUser, accountViewModel, nav)
|
3 -> TabFollows(baseUser, followersFeedViewModel, accountViewModel, nav)
|
||||||
4 -> TabReceivedZaps(baseUser, accountViewModel, nav)
|
4 -> TabReceivedZaps(baseUser, accountViewModel, nav)
|
||||||
5 -> TabBookmarks(baseUser, accountViewModel, nav)
|
5 -> TabBookmarks(baseUser, accountViewModel, nav)
|
||||||
6 -> TabReports(baseUser, accountViewModel, nav)
|
6 -> TabReports(baseUser, accountViewModel, nav)
|
||||||
@@ -307,31 +338,53 @@ private fun ZapTabHeader(baseUser: User) {
|
|||||||
var zapAmount by remember { mutableStateOf<BigDecimal?>(null) }
|
var zapAmount by remember { mutableStateOf<BigDecimal?>(null) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = userState) {
|
LaunchedEffect(key1 = userState) {
|
||||||
withContext(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val tempAmount = baseUser.zappedAmount()
|
val tempAmount = baseUser.zappedAmount()
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
|
if (zapAmount != tempAmount) {
|
||||||
zapAmount = tempAmount
|
zapAmount = tempAmount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text(text = "${showAmount(zapAmount)} ${stringResource(id = R.string.zaps)}")
|
Text(text = "${showAmountAxis(zapAmount)} ${stringResource(id = R.string.zaps)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun FollowersTabHeader(baseUser: User) {
|
private fun FollowersTabHeader(baseUser: User) {
|
||||||
val userState by baseUser.live().follows.observeAsState()
|
val userState by baseUser.live().follows.observeAsState()
|
||||||
val userFollowers = remember(userState) { userState?.user?.transientFollowerCount() ?: "--" }
|
var followerCount by remember { mutableStateOf("--") }
|
||||||
|
|
||||||
Text(text = "$userFollowers ${stringResource(id = R.string.followers)}")
|
LaunchedEffect(key1 = userState) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
val newFollower = userState?.user?.transientFollowerCount()?.toString() ?: "--"
|
||||||
|
|
||||||
|
if (followerCount != newFollower) {
|
||||||
|
followerCount = newFollower
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text = "$followerCount ${stringResource(id = R.string.followers)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun FollowTabHeader(baseUser: User) {
|
private fun FollowTabHeader(baseUser: User) {
|
||||||
val userState by baseUser.live().follows.observeAsState()
|
val userState by baseUser.live().follows.observeAsState()
|
||||||
val userFollows = remember(userState) { userState?.user?.transientFollowCount() ?: "--" }
|
var followCount by remember { mutableStateOf("--") }
|
||||||
|
|
||||||
Text(text = "$userFollows ${stringResource(R.string.follows)}")
|
LaunchedEffect(key1 = userState) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
val newFollow = userState?.user?.transientFollowCount()?.toString() ?: "--"
|
||||||
|
|
||||||
|
if (followCount != newFollow) {
|
||||||
|
followCount = newFollow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(text = "$followCount ${stringResource(R.string.follows)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -389,7 +442,7 @@ private fun ProfileHeader(
|
|||||||
) {
|
) {
|
||||||
UserPicture(
|
UserPicture(
|
||||||
baseUser = baseUser,
|
baseUser = baseUser,
|
||||||
baseUserAccount = accountViewModel.userProfile(),
|
accountViewModel = accountViewModel,
|
||||||
size = 100.dp,
|
size = 100.dp,
|
||||||
modifier = Modifier.border(
|
modifier = Modifier.border(
|
||||||
3.dp,
|
3.dp,
|
||||||
@@ -402,7 +455,7 @@ private fun ProfileHeader(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
ResizeImage(it.info?.picture, 100.dp).proxyUrl()?.let { it1 ->
|
it.info?.picture?.let { it1 ->
|
||||||
clipboardManager.setText(
|
clipboardManager.setText(
|
||||||
AnnotatedString(it1)
|
AnnotatedString(it1)
|
||||||
)
|
)
|
||||||
@@ -465,7 +518,7 @@ private fun ProfileActions(
|
|||||||
|
|
||||||
val isUserFollowingLoggedIn by remember(accountUserState, accountLocalUserState) {
|
val isUserFollowingLoggedIn by remember(accountUserState, accountLocalUserState) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
baseUser.isFollowingCached(accountUser)
|
baseUser.isFollowing(accountUser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -937,41 +990,28 @@ fun TabBookmarks(baseUser: User, accountViewModel: AccountViewModel, nav: (Strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TabFollows(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
fun TabFollows(baseUser: User, feedViewModel: UserFeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val feedViewModel: NostrUserProfileFollowsUserFeedViewModel = viewModel()
|
WatchFollowChanges(baseUser, feedViewModel)
|
||||||
|
|
||||||
val userState by baseUser.live().follows.observeAsState()
|
|
||||||
|
|
||||||
LaunchedEffect(userState) {
|
|
||||||
feedViewModel.invalidateData()
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 0.dp)
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
) {
|
) {
|
||||||
UserFeedView(feedViewModel, accountViewModel, nav, enablePullRefresh = false)
|
RefreshingFeedUserFeedView(feedViewModel, accountViewModel, nav, enablePullRefresh = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TabFollowers(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
private fun WatchFollowChanges(
|
||||||
val feedViewModel: NostrUserProfileFollowersUserFeedViewModel = viewModel()
|
baseUser: User,
|
||||||
|
feedViewModel: UserFeedViewModel
|
||||||
|
) {
|
||||||
val userState by baseUser.live().follows.observeAsState()
|
val userState by baseUser.live().follows.observeAsState()
|
||||||
|
|
||||||
LaunchedEffect(userState) {
|
LaunchedEffect(userState) {
|
||||||
feedViewModel.invalidateData()
|
feedViewModel.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(vertical = 0.dp)
|
|
||||||
) {
|
|
||||||
UserFeedView(feedViewModel, accountViewModel, nav, enablePullRefresh = false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -389,7 +389,7 @@ fun HashtagLine(tag: String, onClick: () -> Unit) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun UserLine(
|
fun UserLine(
|
||||||
baseUser: User,
|
baseUser: User,
|
||||||
account: Account,
|
accountViewModel: AccountViewModel,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@@ -405,7 +405,7 @@ fun UserLine(
|
|||||||
top = 10.dp
|
top = 10.dp
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
UserPicture(baseUser, account.userProfile(), 55.dp, Modifier, null)
|
UserPicture(baseUser, 55.dp, accountViewModel, Modifier, null)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -247,11 +247,6 @@ private fun RenderVideoOrPictureNote(
|
|||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val noteEvent = note.event
|
val noteEvent = note.event
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = accountState?.account ?: return
|
|
||||||
val loggedIn = account.userProfile()
|
|
||||||
|
|
||||||
var moreActionsExpanded by remember { mutableStateOf(false) }
|
var moreActionsExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Column(Modifier.fillMaxSize(1f)) {
|
Column(Modifier.fillMaxSize(1f)) {
|
||||||
@@ -268,7 +263,7 @@ private fun RenderVideoOrPictureNote(
|
|||||||
Column(Modifier.weight(1f)) {
|
Column(Modifier.weight(1f)) {
|
||||||
Row(Modifier.padding(10.dp), verticalAlignment = Alignment.Bottom) {
|
Row(Modifier.padding(10.dp), verticalAlignment = Alignment.Bottom) {
|
||||||
Column(Modifier.size(55.dp), verticalArrangement = Arrangement.Center) {
|
Column(Modifier.size(55.dp), verticalArrangement = Arrangement.Center) {
|
||||||
NoteAuthorPicture(note, nav, loggedIn, 55.dp)
|
NoteAuthorPicture(note, nav, accountViewModel, 55.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|||||||
Reference in New Issue
Block a user