Increases the speed of the Zap Tab in Profiles.
This commit is contained in:
@@ -2,17 +2,21 @@ package com.vitorpamplona.amethyst.service.model.zaps
|
|||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapEventInterface
|
import com.vitorpamplona.amethyst.service.model.LnZapEventInterface
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.ZapReqResponse
|
||||||
|
|
||||||
object UserZaps {
|
object UserZaps {
|
||||||
fun forProfileFeed(zaps: Map<Note, Note?>?): List<Pair<Note, Note>> {
|
fun forProfileFeed(zaps: Map<Note, Note?>?): List<ZapReqResponse> {
|
||||||
if (zaps == null) return emptyList()
|
if (zaps == null) return emptyList()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
zaps
|
zaps
|
||||||
.filter { it.value != null }
|
.mapNotNull { entry ->
|
||||||
.toList()
|
entry.value?.let {
|
||||||
.sortedBy { (it.second?.event as? LnZapEventInterface)?.amount() }
|
ZapReqResponse(entry.key, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sortedBy { (it.zapEvent.event as? LnZapEventInterface)?.amount() }
|
||||||
.reversed()
|
.reversed()
|
||||||
) as List<Pair<Note, Note>>
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -385,11 +386,13 @@ fun Notifying(baseMentions: List<User>?, onClick: (User) -> Unit) {
|
|||||||
|
|
||||||
mentions.forEachIndexed { idx, user ->
|
mentions.forEachIndexed { idx, user ->
|
||||||
val innerUserState by user.live().metadata.observeAsState()
|
val innerUserState by user.live().metadata.observeAsState()
|
||||||
val innerUser = innerUserState?.user
|
innerUserState?.user?.let { myUser ->
|
||||||
|
|
||||||
innerUser?.let { myUser ->
|
|
||||||
Spacer(modifier = Modifier.width(5.dp))
|
Spacer(modifier = Modifier.width(5.dp))
|
||||||
|
|
||||||
|
val tags = remember(innerUserState) {
|
||||||
|
myUser.info?.latestMetadata?.tags?.toImmutableList()
|
||||||
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
shape = RoundedCornerShape(20.dp),
|
shape = RoundedCornerShape(20.dp),
|
||||||
colors = ButtonDefaults.buttonColors(
|
colors = ButtonDefaults.buttonColors(
|
||||||
@@ -400,8 +403,8 @@ fun Notifying(baseMentions: List<User>?, onClick: (User) -> Unit) {
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
CreateTextWithEmoji(
|
CreateTextWithEmoji(
|
||||||
text = "✖ ${myUser.toBestDisplayName()}",
|
text = remember(innerUserState) { "✖ ${myUser.toBestDisplayName()}" },
|
||||||
tags = myUser.info?.latestMetadata?.tags,
|
tags = tags,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import androidx.compose.material.LocalTextStyle
|
|||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -48,6 +49,7 @@ import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
|||||||
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadChannel
|
import com.vitorpamplona.amethyst.ui.note.LoadChannel
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.ImmutableMap
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -269,7 +271,7 @@ private fun DisplayUser(
|
|||||||
val userState by it.live().metadata.observeAsState()
|
val userState by it.live().metadata.observeAsState()
|
||||||
val route = remember(userState) { "User/${it.pubkeyHex}" }
|
val route = remember(userState) { "User/${it.pubkeyHex}" }
|
||||||
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
|
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
|
||||||
val userTags = remember(userState) { userState?.user?.info?.latestMetadata?.tags }
|
val userTags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableList() }
|
||||||
val addedCharts = remember {
|
val addedCharts = remember {
|
||||||
"${nip19.additionalChars} "
|
"${nip19.additionalChars} "
|
||||||
}
|
}
|
||||||
@@ -323,7 +325,7 @@ fun CreateClickableText(
|
|||||||
@Composable
|
@Composable
|
||||||
fun CreateTextWithEmoji(
|
fun CreateTextWithEmoji(
|
||||||
text: String,
|
text: String,
|
||||||
tags: List<List<String>>?,
|
tags: ImmutableList<List<String>>?,
|
||||||
color: Color = Color.Unspecified,
|
color: Color = Color.Unspecified,
|
||||||
textAlign: TextAlign? = null,
|
textAlign: TextAlign? = null,
|
||||||
fontWeight: FontWeight? = null,
|
fontWeight: FontWeight? = null,
|
||||||
@@ -382,7 +384,7 @@ fun CreateTextWithEmoji(
|
|||||||
@Composable
|
@Composable
|
||||||
fun CreateTextWithEmoji(
|
fun CreateTextWithEmoji(
|
||||||
text: String,
|
text: String,
|
||||||
emojis: Map<String, String>,
|
emojis: ImmutableMap<String, String>,
|
||||||
color: Color = Color.Unspecified,
|
color: Color = Color.Unspecified,
|
||||||
textAlign: TextAlign? = null,
|
textAlign: TextAlign? = null,
|
||||||
fontWeight: FontWeight? = null,
|
fontWeight: FontWeight? = null,
|
||||||
@@ -438,7 +440,7 @@ fun CreateTextWithEmoji(
|
|||||||
@Composable
|
@Composable
|
||||||
fun CreateClickableTextWithEmoji(
|
fun CreateClickableTextWithEmoji(
|
||||||
clickablePart: String,
|
clickablePart: String,
|
||||||
tags: List<List<String>>?,
|
tags: ImmutableList<List<String>>?,
|
||||||
style: TextStyle,
|
style: TextStyle,
|
||||||
onClick: (Int) -> Unit
|
onClick: (Int) -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -475,7 +477,7 @@ fun CreateClickableTextWithEmoji(
|
|||||||
fun CreateClickableTextWithEmoji(
|
fun CreateClickableTextWithEmoji(
|
||||||
clickablePart: String,
|
clickablePart: String,
|
||||||
suffix: String,
|
suffix: String,
|
||||||
tags: List<List<String>>?,
|
tags: ImmutableList<List<String>>?,
|
||||||
overrideColor: Color? = null,
|
overrideColor: Color? = null,
|
||||||
fontWeight: FontWeight = FontWeight.Normal,
|
fontWeight: FontWeight = FontWeight.Normal,
|
||||||
route: String,
|
route: String,
|
||||||
@@ -512,7 +514,7 @@ fun CreateClickableTextWithEmoji(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun assembleAnnotatedList(text: String, emojis: Map<String, String>): List<Renderable> {
|
suspend fun assembleAnnotatedList(text: String, emojis: Map<String, String>): ImmutableList<Renderable> {
|
||||||
return NIP30Parser().buildArray(text).map {
|
return NIP30Parser().buildArray(text).map {
|
||||||
val url = emojis[it]
|
val url = emojis[it]
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
@@ -520,15 +522,20 @@ suspend fun assembleAnnotatedList(text: String, emojis: Map<String, String>): Li
|
|||||||
} else {
|
} else {
|
||||||
TextType(it)
|
TextType(it)
|
||||||
}
|
}
|
||||||
}
|
}.toImmutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
open class Renderable()
|
open class Renderable()
|
||||||
|
|
||||||
|
@Immutable
|
||||||
class TextType(val text: String) : Renderable()
|
class TextType(val text: String) : Renderable()
|
||||||
|
|
||||||
|
@Immutable
|
||||||
class ImageUrlType(val url: String) : Renderable()
|
class ImageUrlType(val url: String) : Renderable()
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ClickableInLineIconRenderer(wordsInOrder: List<Renderable>, style: SpanStyle, onClick: (Int) -> Unit) {
|
fun ClickableInLineIconRenderer(wordsInOrder: ImmutableList<Renderable>, style: SpanStyle, onClick: (Int) -> Unit) {
|
||||||
val inlineContent = wordsInOrder.mapIndexedNotNull { idx, value ->
|
val inlineContent = wordsInOrder.mapIndexedNotNull { idx, value ->
|
||||||
if (value is ImageUrlType) {
|
if (value is ImageUrlType) {
|
||||||
Pair(
|
Pair(
|
||||||
@@ -589,7 +596,7 @@ fun ClickableInLineIconRenderer(wordsInOrder: List<Renderable>, style: SpanStyle
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun InLineIconRenderer(
|
fun InLineIconRenderer(
|
||||||
wordsInOrder: List<Renderable>,
|
wordsInOrder: ImmutableList<Renderable>,
|
||||||
style: SpanStyle,
|
style: SpanStyle,
|
||||||
maxLines: Int = Int.MAX_VALUE,
|
maxLines: Int = Int.MAX_VALUE,
|
||||||
overflow: TextOverflow = TextOverflow.Clip,
|
overflow: TextOverflow = TextOverflow.Clip,
|
||||||
|
|||||||
@@ -702,55 +702,90 @@ fun startsWithNIP19Scheme(word: String): Boolean {
|
|||||||
return listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1").any { cleaned.startsWith(it) }
|
return listOf("npub1", "naddr1", "note1", "nprofile1", "nevent1").any { cleaned.startsWith(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class LoadedBechLink(val baseNote: Note?, val nip19: Nip19.Return)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
fun BechLink(word: String, canPreview: Boolean, backgroundColor: Color, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
var nip19Route by remember { mutableStateOf<Nip19.Return?>(null) }
|
var loadedLink by remember { mutableStateOf<LoadedBechLink?>(null) }
|
||||||
var baseNotePair by remember { mutableStateOf<Pair<Note, String?>?>(null) }
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = word) {
|
LaunchedEffect(key1 = word) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
Nip19.uriToRoute(word)?.let {
|
Nip19.uriToRoute(word)?.let {
|
||||||
|
var returningNote: Note? = null
|
||||||
if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
|
if (it.type == Nip19.Type.NOTE || it.type == Nip19.Type.EVENT || it.type == Nip19.Type.ADDRESS) {
|
||||||
LocalCache.checkGetOrCreateNote(it.hex)?.let { note ->
|
LocalCache.checkGetOrCreateNote(it.hex)?.let { note ->
|
||||||
baseNotePair = Pair(note, it.additionalChars)
|
returningNote = note
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nip19Route = it
|
loadedLink = LoadedBechLink(returningNote, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canPreview) {
|
if (canPreview) {
|
||||||
baseNotePair?.let {
|
loadedLink?.let { loadedLink ->
|
||||||
NoteCompose(
|
loadedLink.baseNote?.let {
|
||||||
baseNote = it.first,
|
DisplayFullNote(it, accountViewModel, backgroundColor, nav, loadedLink)
|
||||||
accountViewModel = accountViewModel,
|
} ?: run {
|
||||||
modifier = Modifier
|
ClickableRoute(loadedLink.nip19, nav)
|
||||||
.padding(top = 2.dp, bottom = 0.dp, start = 0.dp, end = 0.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(shape = RoundedCornerShape(15.dp))
|
|
||||||
.border(
|
|
||||||
1.dp,
|
|
||||||
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
|
|
||||||
RoundedCornerShape(15.dp)
|
|
||||||
),
|
|
||||||
parentBackgroundColor = backgroundColor,
|
|
||||||
isQuotedNote = true,
|
|
||||||
nav = nav
|
|
||||||
)
|
|
||||||
if (!it.second.isNullOrEmpty()) {
|
|
||||||
Text(
|
|
||||||
"${it.second} "
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} ?: nip19Route?.let {
|
} ?: run {
|
||||||
ClickableRoute(it, nav)
|
Text(text = remember { "$word " })
|
||||||
} ?: Text(text = "$word ")
|
}
|
||||||
} else {
|
} else {
|
||||||
nip19Route?.let {
|
loadedLink?.let {
|
||||||
ClickableRoute(it, nav)
|
ClickableRoute(it.nip19, nav)
|
||||||
} ?: Text(text = "$word ")
|
} ?: run {
|
||||||
|
Text(text = remember { "$word " })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DisplayFullNote(
|
||||||
|
it: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
backgroundColor: Color,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
loadedLink: LoadedBechLink
|
||||||
|
) {
|
||||||
|
val borderColor = MaterialTheme.colors.onSurface.copy(alpha = 0.12f)
|
||||||
|
|
||||||
|
val modifier = remember {
|
||||||
|
Modifier
|
||||||
|
.padding(top = 2.dp, bottom = 0.dp, start = 0.dp, end = 0.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(shape = RoundedCornerShape(15.dp))
|
||||||
|
.border(
|
||||||
|
1.dp,
|
||||||
|
borderColor,
|
||||||
|
RoundedCornerShape(15.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteCompose(
|
||||||
|
baseNote = it,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
modifier = modifier,
|
||||||
|
parentBackgroundColor = backgroundColor,
|
||||||
|
isQuotedNote = true,
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
|
||||||
|
val extraChars = remember(loadedLink) {
|
||||||
|
if (loadedLink.nip19.additionalChars.isNotBlank()) {
|
||||||
|
"${loadedLink.nip19.additionalChars} "
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extraChars?.let {
|
||||||
|
Text(
|
||||||
|
it
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -876,12 +911,12 @@ fun TagLink(word: String, tags: List<List<String>>, canPreview: Boolean, backgro
|
|||||||
"User/${it.first.pubkeyHex}"
|
"User/${it.first.pubkeyHex}"
|
||||||
}
|
}
|
||||||
val userTags = remember(innerUserState) {
|
val userTags = remember(innerUserState) {
|
||||||
innerUserState?.user?.info?.latestMetadata?.tags
|
innerUserState?.user?.info?.latestMetadata?.tags?.toImmutableList()
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateClickableTextWithEmoji(
|
CreateClickableTextWithEmoji(
|
||||||
clickablePart = displayName,
|
clickablePart = displayName,
|
||||||
suffix = "${it.second} ",
|
suffix = remember { "${it.second} " },
|
||||||
tags = userTags,
|
tags = userTags,
|
||||||
route = route,
|
route = route,
|
||||||
nav = nav
|
nav = nav
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.dal
|
package com.vitorpamplona.amethyst.ui.dal
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.model.zaps.UserZaps
|
import com.vitorpamplona.amethyst.service.model.zaps.UserZaps
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.ZapReqResponse
|
||||||
|
|
||||||
object UserProfileZapsFeedFilter : FeedFilter<Pair<Note, Note>>() {
|
class UserProfileZapsFeedFilter(val user: User) : FeedFilter<ZapReqResponse>() {
|
||||||
var user: User? = null
|
override fun feed(): List<ZapReqResponse> {
|
||||||
|
return UserZaps.forProfileFeed(user.zaps)
|
||||||
fun loadUserProfile(user: User?) {
|
|
||||||
this.user = user
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun feed(): List<Pair<Note, Note>> {
|
|
||||||
return UserZaps.forProfileFeed(user?.zaps)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -57,6 +57,7 @@ import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage
|
import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginPage
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -227,7 +228,7 @@ private fun AccountName(
|
|||||||
}
|
}
|
||||||
val tags by remember(userState) {
|
val tags by remember(userState) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
user.info?.latestMetadata?.tags
|
user.info?.latestMetadata?.tags?.toImmutableList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ fun ProfileContent(
|
|||||||
val profilePicture = remember(accountUserState) { accountUserState?.user?.profilePicture()?.ifBlank { null }?.let { ResizeImage(it, 100.dp) } }
|
val profilePicture = remember(accountUserState) { accountUserState?.user?.profilePicture()?.ifBlank { null }?.let { ResizeImage(it, 100.dp) } }
|
||||||
val bestUserName = remember(accountUserState) { accountUserState?.user?.bestUsername() }
|
val bestUserName = remember(accountUserState) { accountUserState?.user?.bestUsername() }
|
||||||
val bestDisplayName = remember(accountUserState) { accountUserState?.user?.bestDisplayName() }
|
val bestDisplayName = remember(accountUserState) { accountUserState?.user?.bestDisplayName() }
|
||||||
val tags = remember(accountUserState) { accountUserState?.user?.info?.latestMetadata?.tags }
|
val tags = remember(accountUserState) { accountUserState?.user?.info?.latestMetadata?.tags?.toImmutableList() }
|
||||||
val route = remember(accountUserState) { "User/${accountUserState?.user?.pubkeyHex}" }
|
val route = remember(accountUserState) { "User/${accountUserState?.user?.pubkeyHex}" }
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
@@ -193,8 +194,8 @@ fun ProfileContent(
|
|||||||
}
|
}
|
||||||
if (bestUserName != null) {
|
if (bestUserName != null) {
|
||||||
CreateTextWithEmoji(
|
CreateTextWithEmoji(
|
||||||
text = " @$bestUserName",
|
text = remember { " @$bestUserName" },
|
||||||
tags = accountUser.info?.latestMetadata?.tags,
|
tags = tags,
|
||||||
color = Color.LightGray,
|
color = Color.LightGray,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
|||||||
@@ -418,7 +418,7 @@ private fun RenderChangeChannelMetadataNote(
|
|||||||
|
|
||||||
CreateTextWithEmoji(
|
CreateTextWithEmoji(
|
||||||
text = text,
|
text = text,
|
||||||
tags = note.author?.info?.latestMetadata?.tags
|
tags = remember { note.author?.info?.latestMetadata?.tags?.toImmutableList() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,7 +441,7 @@ private fun RenderCreateChannelNote(note: Note) {
|
|||||||
|
|
||||||
CreateTextWithEmoji(
|
CreateTextWithEmoji(
|
||||||
text = text,
|
text = text,
|
||||||
tags = note.author?.info?.latestMetadata?.tags
|
tags = remember { note.author?.info?.latestMetadata?.tags?.toImmutableList() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +457,7 @@ private fun DrawAuthorInfo(
|
|||||||
val route = remember { "User/$pubkeyHex" }
|
val route = remember { "User/$pubkeyHex" }
|
||||||
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
|
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
|
||||||
val userProfilePicture = remember(userState) { ResizeImage(userState?.user?.profilePicture(), 25.dp) }
|
val userProfilePicture = remember(userState) { ResizeImage(userState?.user?.profilePicture(), 25.dp) }
|
||||||
val userTags = remember(userState) { userState?.user?.info?.latestMetadata?.tags }
|
val userTags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableList() }
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -468,17 +468,19 @@ private fun DrawAuthorInfo(
|
|||||||
robot = pubkeyHex,
|
robot = pubkeyHex,
|
||||||
model = userProfilePicture,
|
model = userProfilePicture,
|
||||||
contentDescription = stringResource(id = R.string.profile_image),
|
contentDescription = stringResource(id = R.string.profile_image),
|
||||||
modifier = Modifier
|
modifier = remember {
|
||||||
.width(25.dp)
|
Modifier
|
||||||
.height(25.dp)
|
.width(25.dp)
|
||||||
.clip(shape = CircleShape)
|
.height(25.dp)
|
||||||
.clickable(onClick = {
|
.clip(shape = CircleShape)
|
||||||
nav(route)
|
.clickable(onClick = {
|
||||||
})
|
nav(route)
|
||||||
|
})
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
CreateClickableTextWithEmoji(
|
CreateClickableTextWithEmoji(
|
||||||
clickablePart = " $userDisplayName",
|
clickablePart = remember { " $userDisplayName" },
|
||||||
suffix = "",
|
suffix = "",
|
||||||
tags = userTags,
|
tags = userTags,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
|
|||||||
@@ -792,18 +792,19 @@ fun RenderAppDefinition(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
it.anyName()?.let {
|
val name = remember(it) { it.anyName() }
|
||||||
|
name?.let {
|
||||||
Row(verticalAlignment = Alignment.Bottom, modifier = Modifier.padding(top = 7.dp)) {
|
Row(verticalAlignment = Alignment.Bottom, modifier = Modifier.padding(top = 7.dp)) {
|
||||||
CreateTextWithEmoji(
|
CreateTextWithEmoji(
|
||||||
text = it,
|
text = it,
|
||||||
tags = remember { note.event?.tags() ?: emptyList() },
|
tags = remember { (note.event?.tags() ?: emptyList()).toImmutableList() },
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 25.sp
|
fontSize = 25.sp
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val website = it.website
|
val website = remember(it) { it.website }
|
||||||
if (!website.isNullOrEmpty()) {
|
if (!website.isNullOrEmpty()) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -1717,7 +1718,7 @@ fun DisplayHighlight(
|
|||||||
val userState by userBase.live().metadata.observeAsState()
|
val userState by userBase.live().metadata.observeAsState()
|
||||||
val route = remember { "User/${userBase.pubkeyHex}" }
|
val route = remember { "User/${userBase.pubkeyHex}" }
|
||||||
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
|
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
|
||||||
val userTags = remember(userState) { userState?.user?.info?.latestMetadata?.tags }
|
val userTags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableList() }
|
||||||
|
|
||||||
if (userDisplayName != null) {
|
if (userDisplayName != null) {
|
||||||
CreateClickableTextWithEmoji(
|
CreateClickableTextWithEmoji(
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.vitorpamplona.amethyst.R
|
|||||||
import com.vitorpamplona.amethyst.model.*
|
import com.vitorpamplona.amethyst.model.*
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -231,17 +232,14 @@ private fun ReplyInfoMention(
|
|||||||
onUserTagClick: (User) -> Unit
|
onUserTagClick: (User) -> Unit
|
||||||
) {
|
) {
|
||||||
val innerUserState by user.live().metadata.observeAsState()
|
val innerUserState by user.live().metadata.observeAsState()
|
||||||
val innerUser = remember(innerUserState) {
|
|
||||||
innerUserState?.user
|
|
||||||
} ?: return
|
|
||||||
|
|
||||||
CreateClickableTextWithEmoji(
|
CreateClickableTextWithEmoji(
|
||||||
clickablePart = "$prefix${innerUser.toBestDisplayName()}",
|
clickablePart = remember(innerUserState) { "$prefix${innerUserState?.user?.toBestDisplayName()}" },
|
||||||
tags = innerUser.info?.latestMetadata?.tags,
|
tags = remember(innerUserState) { innerUserState?.user?.info?.latestMetadata?.tags?.toImmutableList() },
|
||||||
style = LocalTextStyle.current.copy(
|
style = LocalTextStyle.current.copy(
|
||||||
color = MaterialTheme.colors.primary.copy(alpha = 0.52f),
|
color = MaterialTheme.colors.primary.copy(alpha = 0.52f),
|
||||||
fontSize = 13.sp
|
fontSize = 13.sp
|
||||||
),
|
),
|
||||||
onClick = { onUserTagClick(innerUser) }
|
onClick = { onUserTagClick(user) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NoteUsernameDisplay(baseNote: Note, weight: Modifier = Modifier) {
|
fun NoteUsernameDisplay(baseNote: Note, weight: Modifier = Modifier) {
|
||||||
@@ -29,7 +31,7 @@ fun UsernameDisplay(baseUser: User, weight: Modifier = Modifier) {
|
|||||||
val bestUserName = remember(userState) { userState?.user?.bestUsername() }
|
val bestUserName = remember(userState) { userState?.user?.bestUsername() }
|
||||||
val bestDisplayName = remember(userState) { userState?.user?.bestDisplayName() }
|
val bestDisplayName = remember(userState) { userState?.user?.bestDisplayName() }
|
||||||
val npubDisplay = remember { baseUser.pubkeyDisplayHex() }
|
val npubDisplay = remember { baseUser.pubkeyDisplayHex() }
|
||||||
val tags = remember(userState) { userState?.user?.info?.latestMetadata?.tags }
|
val tags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableList() }
|
||||||
|
|
||||||
UserNameDisplay(bestUserName, bestDisplayName, npubDisplay, tags, weight)
|
UserNameDisplay(bestUserName, bestDisplayName, npubDisplay, tags, weight)
|
||||||
}
|
}
|
||||||
@@ -39,7 +41,7 @@ private fun UserNameDisplay(
|
|||||||
bestUserName: String?,
|
bestUserName: String?,
|
||||||
bestDisplayName: String?,
|
bestDisplayName: String?,
|
||||||
npubDisplay: String,
|
npubDisplay: String,
|
||||||
tags: List<List<String>>?,
|
tags: ImmutableList<List<String>>?,
|
||||||
modifier: Modifier
|
modifier: Modifier
|
||||||
) {
|
) {
|
||||||
if (bestUserName != null && bestDisplayName != null) {
|
if (bestUserName != null && bestDisplayName != null) {
|
||||||
@@ -49,7 +51,7 @@ private fun UserNameDisplay(
|
|||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
CreateTextWithEmoji(
|
CreateTextWithEmoji(
|
||||||
text = "@$bestUserName",
|
text = remember { "@$bestUserName" },
|
||||||
tags = tags,
|
tags = tags,
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
@@ -67,7 +69,7 @@ private fun UserNameDisplay(
|
|||||||
)
|
)
|
||||||
} else if (bestUserName != null) {
|
} else if (bestUserName != null) {
|
||||||
CreateTextWithEmoji(
|
CreateTextWithEmoji(
|
||||||
text = "@$bestUserName",
|
text = remember { "@$bestUserName" },
|
||||||
tags = tags,
|
tags = tags,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.ZapReqResponse
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.FollowButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.FollowButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShowUserButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShowUserButton
|
||||||
@@ -38,37 +39,44 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
fun ZapNoteCompose(baseReqResponse: ZapReqResponse, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val baseNoteRequest by baseNote.first.live().metadata.observeAsState()
|
val baseNoteRequest by baseReqResponse.request.live().metadata.observeAsState()
|
||||||
val noteZapRequest = remember(baseNoteRequest) { baseNoteRequest?.note } ?: return
|
|
||||||
|
|
||||||
var baseAuthor by remember {
|
var baseAuthor by remember {
|
||||||
mutableStateOf(noteZapRequest.author)
|
mutableStateOf<User?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(baseNoteRequest) {
|
||||||
|
launch(Dispatchers.Default) {
|
||||||
|
(baseNoteRequest?.note?.event as? LnZapRequestEvent)?.let {
|
||||||
|
baseNoteRequest?.note?.let {
|
||||||
|
val decryptedContent = accountViewModel.decryptZap(it)
|
||||||
|
if (decryptedContent != null) {
|
||||||
|
baseAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey)
|
||||||
|
} else {
|
||||||
|
baseAuthor = it.author
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseAuthor == null) {
|
if (baseAuthor == null) {
|
||||||
BlankNote()
|
BlankNote()
|
||||||
} else {
|
} else {
|
||||||
|
val route = remember(baseAuthor) {
|
||||||
|
"User/${baseAuthor?.pubkeyHex}"
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.clickable(
|
Modifier.clickable(
|
||||||
onClick = { nav("User/${baseAuthor?.pubkeyHex}") }
|
onClick = { nav(route) }
|
||||||
),
|
),
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
launch(Dispatchers.Default) {
|
|
||||||
(noteZapRequest.event as? LnZapRequestEvent)?.let {
|
|
||||||
val decryptedContent = accountViewModel.decryptZap(noteZapRequest)
|
|
||||||
if (decryptedContent != null) {
|
|
||||||
baseAuthor = LocalCache.getOrCreateUser(decryptedContent.pubKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
baseAuthor?.let {
|
baseAuthor?.let {
|
||||||
RenderZapNote(it, baseNote.second, nav, accountViewModel)
|
RenderZapNote(it, baseReqResponse.zapEvent, nav, accountViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider(
|
Divider(
|
||||||
@@ -87,30 +95,37 @@ private fun RenderZapNote(
|
|||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = remember {
|
||||||
.padding(
|
Modifier
|
||||||
start = 12.dp,
|
.padding(
|
||||||
end = 12.dp,
|
start = 12.dp,
|
||||||
top = 10.dp
|
end = 12.dp,
|
||||||
),
|
top = 10.dp
|
||||||
|
)
|
||||||
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
UserPicture(baseAuthor, nav, accountViewModel, 55.dp)
|
UserPicture(baseAuthor, nav, accountViewModel, 55.dp)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = remember {
|
||||||
.padding(start = 10.dp)
|
Modifier
|
||||||
.weight(1f)
|
.padding(start = 10.dp)
|
||||||
|
.weight(1f)
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
UsernameDisplay(baseAuthor)
|
UsernameDisplay(baseAuthor)
|
||||||
}
|
}
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
AboutDisplay(baseAuthor)
|
AboutDisplay(baseAuthor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(start = 10.dp),
|
modifier = remember {
|
||||||
|
Modifier.padding(start = 10.dp)
|
||||||
|
},
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
ZapAmount(zapNote)
|
ZapAmount(zapNote)
|
||||||
@@ -125,13 +140,15 @@ private fun RenderZapNote(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ZapAmount(zapEventNote: Note) {
|
private fun ZapAmount(zapEventNote: Note) {
|
||||||
val noteState by zapEventNote.live().metadata.observeAsState()
|
val noteState by zapEventNote.live().metadata.observeAsState()
|
||||||
val noteZap = remember(noteState) { noteState?.note } ?: return
|
|
||||||
|
|
||||||
var zapAmount by remember { mutableStateOf<String?>(null) }
|
var zapAmount by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = noteZap) {
|
LaunchedEffect(key1 = noteState) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
zapAmount = showAmountAxis((noteZap.event as? LnZapEvent)?.amount)
|
val newZapAmount = showAmountAxis((noteState?.note?.event as? LnZapEvent)?.amount)
|
||||||
|
if (zapAmount != newZapAmount) {
|
||||||
|
zapAmount = newZapAmount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +168,6 @@ fun UserActionOptions(
|
|||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val isHidden by remember(accountState) {
|
val isHidden by remember(accountState) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@@ -159,20 +175,39 @@ fun UserActionOptions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val userState by accountViewModel.account.userProfile().live().follows.observeAsState()
|
|
||||||
val isFollowing by remember(userState) {
|
|
||||||
derivedStateOf {
|
|
||||||
userState?.user?.isFollowingCached(baseAuthor) ?: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isHidden) {
|
if (isHidden) {
|
||||||
ShowUserButton {
|
ShowUserButton {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
accountViewModel.show(baseAuthor)
|
accountViewModel.show(baseAuthor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (isFollowing) {
|
} else {
|
||||||
|
ShowFollowingOrUnfollowingButton(baseAuthor, accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShowFollowingOrUnfollowingButton(
|
||||||
|
baseAuthor: User,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
var isFollowing by remember { mutableStateOf(false) }
|
||||||
|
val accountFollowsState by accountViewModel.account.userProfile().live().follows.observeAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = accountFollowsState) {
|
||||||
|
launch(Dispatchers.Default) {
|
||||||
|
val newShowFollowingMark =
|
||||||
|
accountFollowsState?.user?.isFollowing(baseAuthor) == true
|
||||||
|
|
||||||
|
if (newShowFollowingMark != isFollowing) {
|
||||||
|
isFollowing = newShowFollowingMark
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFollowing) {
|
||||||
UnfollowButton { scope.launch(Dispatchers.IO) { accountViewModel.unfollow(baseAuthor) } }
|
UnfollowButton { scope.launch(Dispatchers.IO) { accountViewModel.unfollow(baseAuthor) } }
|
||||||
} else {
|
} else {
|
||||||
FollowButton({ scope.launch(Dispatchers.IO) { accountViewModel.follow(baseAuthor) } })
|
FollowButton({ scope.launch(Dispatchers.IO) { accountViewModel.follow(baseAuthor) } })
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
|||||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
import com.vitorpamplona.amethyst.ui.qrcode.NIP19QrCodeScanner
|
import com.vitorpamplona.amethyst.ui.qrcode.NIP19QrCodeScanner
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
|
fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
|
||||||
@@ -87,7 +88,7 @@ fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
|
|||||||
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth().padding(top = 5.dp)) {
|
Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth().padding(top = 5.dp)) {
|
||||||
CreateTextWithEmoji(
|
CreateTextWithEmoji(
|
||||||
text = user.bestDisplayName() ?: user.bestUsername() ?: "",
|
text = user.bestDisplayName() ?: user.bestUsername() ?: "",
|
||||||
tags = user.info?.latestMetadata?.tags,
|
tags = user.info?.latestMetadata?.tags?.toImmutableList(),
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 18.sp
|
fontSize = 18.sp
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class ZapReqResponse(val request: Note, val zapEvent: Note)
|
||||||
|
|
||||||
|
@Stable
|
||||||
sealed class LnZapFeedState {
|
sealed class LnZapFeedState {
|
||||||
object Loading : LnZapFeedState()
|
object Loading : LnZapFeedState()
|
||||||
class Loaded(val feed: MutableState<List<Pair<Note, Note>>>) : LnZapFeedState()
|
class Loaded(val feed: MutableState<ImmutableList<ZapReqResponse>>) : LnZapFeedState()
|
||||||
object Empty : LnZapFeedState()
|
object Empty : LnZapFeedState()
|
||||||
class FeedError(val errorMessage: String) : LnZapFeedState()
|
class FeedError(val errorMessage: String) : LnZapFeedState()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,78 +2,44 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
|
||||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
|
||||||
import androidx.compose.material.pullrefresh.pullRefresh
|
|
||||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapNoteCompose
|
import com.vitorpamplona.amethyst.ui.note.ZapNoteCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LnZapFeedView(
|
fun LnZapFeedView(
|
||||||
viewModel: LnZapFeedViewModel,
|
viewModel: LnZapFeedViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit
|
||||||
enablePullRefresh: Boolean = true
|
|
||||||
) {
|
) {
|
||||||
val feedState by viewModel.feedContent.collectAsState()
|
val feedState by viewModel.feedContent.collectAsState()
|
||||||
|
|
||||||
var refreshing by remember { mutableStateOf(false) }
|
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
||||||
val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false }
|
when (state) {
|
||||||
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
is LnZapFeedState.Empty -> {
|
||||||
|
FeedEmpty {
|
||||||
val modifier = if (enablePullRefresh) {
|
viewModel.invalidateData()
|
||||||
Modifier.pullRefresh(pullRefreshState)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(modifier) {
|
|
||||||
Column() {
|
|
||||||
Crossfade(targetState = feedState, animationSpec = tween(durationMillis = 100)) { state ->
|
|
||||||
when (state) {
|
|
||||||
is LnZapFeedState.Empty -> {
|
|
||||||
FeedEmpty {
|
|
||||||
refreshing = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is LnZapFeedState.FeedError -> {
|
|
||||||
FeedError(state.errorMessage) {
|
|
||||||
refreshing = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is LnZapFeedState.Loaded -> {
|
|
||||||
if (refreshing) {
|
|
||||||
refreshing = false
|
|
||||||
}
|
|
||||||
|
|
||||||
LnZapFeedLoaded(state, accountViewModel, nav)
|
|
||||||
}
|
|
||||||
is LnZapFeedState.Loading -> {
|
|
||||||
LoadingFeed()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
is LnZapFeedState.FeedError -> {
|
||||||
|
FeedError(state.errorMessage) {
|
||||||
if (enablePullRefresh) {
|
viewModel.invalidateData()
|
||||||
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
}
|
||||||
|
}
|
||||||
|
is LnZapFeedState.Loaded -> {
|
||||||
|
LnZapFeedLoaded(state, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
is LnZapFeedState.Loading -> {
|
||||||
|
LoadingFeed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +59,7 @@ private fun LnZapFeedLoaded(
|
|||||||
),
|
),
|
||||||
state = listState
|
state = listState
|
||||||
) {
|
) {
|
||||||
itemsIndexed(state.feed.value, key = { _, item -> item.second.idHex }) { _, item ->
|
itemsIndexed(state.feed.value, key = { _, item -> item.zapEvent.idHex }) { _, item ->
|
||||||
ZapNoteCompose(item, accountViewModel = accountViewModel, nav = nav)
|
ZapNoteCompose(item, accountViewModel = accountViewModel, nav = nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ package com.vitorpamplona.amethyst.ui.screen
|
|||||||
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
|
||||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileZapsFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileZapsFeedFilter
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -17,9 +20,15 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class NostrUserProfileZapsFeedViewModel : LnZapFeedViewModel(UserProfileZapsFeedFilter)
|
class NostrUserProfileZapsFeedViewModel(user: User) : LnZapFeedViewModel(UserProfileZapsFeedFilter(user)) {
|
||||||
|
class Factory(val user: User) : ViewModelProvider.Factory {
|
||||||
|
override fun <NostrUserProfileZapsFeedViewModel : ViewModel> create(modelClass: Class<NostrUserProfileZapsFeedViewModel>): NostrUserProfileZapsFeedViewModel {
|
||||||
|
return NostrUserProfileZapsFeedViewModel(user) as NostrUserProfileZapsFeedViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open class LnZapFeedViewModel(val dataSource: FeedFilter<Pair<Note, Note>>) : ViewModel() {
|
open class LnZapFeedViewModel(val dataSource: FeedFilter<ZapReqResponse>) : ViewModel() {
|
||||||
private val _feedContent = MutableStateFlow<LnZapFeedState>(LnZapFeedState.Loading)
|
private val _feedContent = MutableStateFlow<LnZapFeedState>(LnZapFeedState.Loading)
|
||||||
val feedContent = _feedContent.asStateFlow()
|
val feedContent = _feedContent.asStateFlow()
|
||||||
|
|
||||||
@@ -32,12 +41,12 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter<Pair<Note, Note>>) : Vi
|
|||||||
|
|
||||||
private fun refreshSuspended() {
|
private fun refreshSuspended() {
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
val notes = dataSource.loadTop()
|
val notes = dataSource.loadTop().toImmutableList()
|
||||||
|
|
||||||
val oldNotesState = _feedContent.value
|
val oldNotesState = _feedContent.value
|
||||||
if (oldNotesState is LnZapFeedState.Loaded) {
|
if (oldNotesState is LnZapFeedState.Loaded) {
|
||||||
// Using size as a proxy for has changed.
|
// Using size as a proxy for has changed.
|
||||||
if (notes != oldNotesState.feed.value) {
|
if (!equalImmutableLists(notes, oldNotesState.feed.value)) {
|
||||||
updateFeed(notes)
|
updateFeed(notes)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -45,7 +54,7 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter<Pair<Note, Note>>) : Vi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFeed(notes: List<Pair<Note, Note>>) {
|
private fun updateFeed(notes: ImmutableList<ZapReqResponse>) {
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val currentState = _feedContent.value
|
val currentState = _feedContent.value
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ import com.vitorpamplona.amethyst.ui.dal.UserProfileBookmarksFeedFilter
|
|||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileConversationsFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileConversationsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.UserProfileZapsFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.ShowQRDialog
|
import com.vitorpamplona.amethyst.ui.navigation.ShowQRDialog
|
||||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||||
import com.vitorpamplona.amethyst.ui.screen.FeedState
|
import com.vitorpamplona.amethyst.ui.screen.FeedState
|
||||||
@@ -154,11 +153,19 @@ fun PrepareViewModels(baseUser: User, accountViewModel: AccountViewModel, nav: (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val zapFeedViewModel: NostrUserProfileZapsFeedViewModel = viewModel(
|
||||||
|
key = baseUser.pubkeyHex + "UserProfileZapsFeedViewModel",
|
||||||
|
factory = NostrUserProfileZapsFeedViewModel.Factory(
|
||||||
|
baseUser
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
ProfileScreen(
|
ProfileScreen(
|
||||||
baseUser = baseUser,
|
baseUser = baseUser,
|
||||||
followsFeedViewModel,
|
followsFeedViewModel,
|
||||||
followersFeedViewModel,
|
followersFeedViewModel,
|
||||||
appRecommendations,
|
appRecommendations,
|
||||||
|
zapFeedViewModel,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav
|
nav = nav
|
||||||
)
|
)
|
||||||
@@ -171,12 +178,12 @@ fun ProfileScreen(
|
|||||||
followsFeedViewModel: NostrUserProfileFollowsUserFeedViewModel,
|
followsFeedViewModel: NostrUserProfileFollowsUserFeedViewModel,
|
||||||
followersFeedViewModel: NostrUserProfileFollowersUserFeedViewModel,
|
followersFeedViewModel: NostrUserProfileFollowersUserFeedViewModel,
|
||||||
appRecommendations: NostrUserAppRecommendationsFeedViewModel,
|
appRecommendations: NostrUserAppRecommendationsFeedViewModel,
|
||||||
|
zapFeedViewModel: NostrUserProfileZapsFeedViewModel,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
UserProfileNewThreadFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
UserProfileNewThreadFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
||||||
UserProfileConversationsFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
UserProfileConversationsFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
||||||
UserProfileZapsFeedFilter.loadUserProfile(baseUser)
|
|
||||||
UserProfileReportsFeedFilter.loadUserProfile(baseUser)
|
UserProfileReportsFeedFilter.loadUserProfile(baseUser)
|
||||||
UserProfileBookmarksFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
UserProfileBookmarksFeedFilter.loadUserProfile(accountViewModel.account, baseUser)
|
||||||
|
|
||||||
@@ -293,10 +300,10 @@ fun ProfileScreen(
|
|||||||
1 -> TabNotesConversations(accountViewModel, nav)
|
1 -> TabNotesConversations(accountViewModel, nav)
|
||||||
2 -> TabFollows(baseUser, followsFeedViewModel, accountViewModel, nav)
|
2 -> TabFollows(baseUser, followsFeedViewModel, accountViewModel, nav)
|
||||||
3 -> TabFollows(baseUser, followersFeedViewModel, accountViewModel, nav)
|
3 -> TabFollows(baseUser, followersFeedViewModel, accountViewModel, nav)
|
||||||
4 -> TabReceivedZaps(baseUser, accountViewModel, nav)
|
4 -> TabReceivedZaps(baseUser, zapFeedViewModel, accountViewModel, nav)
|
||||||
6 -> TabBookmarks(baseUser, accountViewModel, nav)
|
5 -> TabBookmarks(baseUser, accountViewModel, nav)
|
||||||
7 -> TabReports(baseUser, accountViewModel, nav)
|
6 -> TabReports(baseUser, accountViewModel, nav)
|
||||||
8 -> TabRelays(baseUser, accountViewModel)
|
7 -> TabRelays(baseUser, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -579,7 +586,7 @@ private fun DrawAdditionalInfo(
|
|||||||
) {
|
) {
|
||||||
val userState by baseUser.live().metadata.observeAsState()
|
val userState by baseUser.live().metadata.observeAsState()
|
||||||
val user = remember(userState) { userState?.user } ?: return
|
val user = remember(userState) { userState?.user } ?: return
|
||||||
val tags = remember(userState) { userState?.user?.info?.latestMetadata?.tags }
|
val tags = remember(userState) { userState?.user?.info?.latestMetadata?.tags?.toImmutableList() }
|
||||||
|
|
||||||
val uri = LocalUriHandler.current
|
val uri = LocalUriHandler.current
|
||||||
val clipboardManager = LocalClipboardManager.current
|
val clipboardManager = LocalClipboardManager.current
|
||||||
@@ -1120,16 +1127,14 @@ private fun WatchFollowChanges(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TabReceivedZaps(baseUser: User, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
fun TabReceivedZaps(baseUser: User, zapFeedViewModel: NostrUserProfileZapsFeedViewModel, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val feedViewModel: NostrUserProfileZapsFeedViewModel = viewModel()
|
WatchZapsAndUpdateFeed(baseUser, zapFeedViewModel)
|
||||||
|
|
||||||
WatchZapsAndUpdateFeed(baseUser, feedViewModel)
|
|
||||||
|
|
||||||
Column(Modifier.fillMaxHeight()) {
|
Column(Modifier.fillMaxHeight()) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(vertical = 0.dp)
|
modifier = Modifier.padding(vertical = 0.dp)
|
||||||
) {
|
) {
|
||||||
LnZapFeedView(feedViewModel, accountViewModel, nav, enablePullRefresh = false)
|
LnZapFeedView(zapFeedViewModel, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user