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