Moves channel checks to happen before a Channel object is loaded.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.model
|
package com.vitorpamplona.amethyst.model
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
@@ -9,6 +10,7 @@ import fr.acinq.secp256k1.Hex
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
|
@Stable
|
||||||
class Channel(val idHex: String) {
|
class Channel(val idHex: String) {
|
||||||
var creator: User? = null
|
var creator: User? = null
|
||||||
var info = ChannelCreateEvent.ChannelData(null, null, null)
|
var info = ChannelCreateEvent.ChannelData(null, null, null)
|
||||||
@@ -33,6 +35,10 @@ class Channel(val idHex: String) {
|
|||||||
notes.remove(note.idHex)
|
notes.remove(note.idHex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeNote(noteHex: String) {
|
||||||
|
notes.remove(noteHex)
|
||||||
|
}
|
||||||
|
|
||||||
fun updateChannelInfo(creator: User, channelInfo: ChannelCreateEvent.ChannelData, updatedAt: Long) {
|
fun updateChannelInfo(creator: User, channelInfo: ChannelCreateEvent.ChannelData, updatedAt: Long) {
|
||||||
this.creator = creator
|
this.creator = creator
|
||||||
this.info = channelInfo
|
this.info = channelInfo
|
||||||
|
|||||||
@@ -429,8 +429,10 @@ object LocalCache {
|
|||||||
masterNote.removeReport(deleteNote)
|
masterNote.removeReport(deleteNote)
|
||||||
}
|
}
|
||||||
|
|
||||||
val channel = deleteNote.channel()
|
deleteNote.channelHex()?.let {
|
||||||
channel?.removeNote(deleteNote)
|
val channel = checkGetOrCreateChannel(it)
|
||||||
|
channel?.removeNote(deleteNote)
|
||||||
|
}
|
||||||
|
|
||||||
if (deleteNote.event is PrivateDmEvent) {
|
if (deleteNote.event is PrivateDmEvent) {
|
||||||
val author = deleteNote.author
|
val author = deleteNote.author
|
||||||
|
|||||||
@@ -73,10 +73,6 @@ open class Note(val idHex: String) {
|
|||||||
?: (event as? ChannelCreateEvent)?.id
|
?: (event as? ChannelCreateEvent)?.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fun channel(): Channel? {
|
|
||||||
return channelHex()?.let { LocalCache.checkGetOrCreateChannel(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun address(): ATag? = null
|
open fun address(): ATag? = null
|
||||||
|
|
||||||
open fun createdAt() = event?.createdAt()
|
open fun createdAt() = event?.createdAt()
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ class ChannelMessageEvent(
|
|||||||
sig: HexKey
|
sig: HexKey
|
||||||
) : BaseTextNoteEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
) : BaseTextNoteEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
|
||||||
fun channel() = tags.firstOrNull { it[0] == "e" && it.size > 3 && it[3] == "root" }?.getOrNull(1) ?: tags.firstOrNull { it.firstOrNull() == "e" }?.getOrNull(1)
|
fun channel() = tags.firstOrNull {
|
||||||
|
it.size > 3 && it[0] == "e" && it[3] == "root"
|
||||||
|
}?.get(1) ?: tags.firstOrNull {
|
||||||
|
it.size > 1 && it[0] == "e"
|
||||||
|
}?.get(1)
|
||||||
|
|
||||||
override fun replyTos() = tags.filter { it.firstOrNull() == "e" && it.getOrNull(1) != channel() }.mapNotNull { it.getOrNull(1) }
|
override fun replyTos() = tags.filter { it.firstOrNull() == "e" && it.getOrNull(1) != channel() }.mapNotNull { it.getOrNull(1) }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class ChannelMetadataEvent(
|
|||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun channel() = tags.firstOrNull { it.firstOrNull() == "e" }?.getOrNull(1)
|
fun channel() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.get(1)
|
||||||
fun channelInfo() =
|
fun channelInfo() =
|
||||||
try {
|
try {
|
||||||
MetadataEvent.gson.fromJson(content, ChannelCreateEvent.ChannelData::class.java)
|
MetadataEvent.gson.fromJson(content, ChannelCreateEvent.ChannelData::class.java)
|
||||||
|
|||||||
@@ -23,14 +23,12 @@ class ContactListEvent(
|
|||||||
// This function is only used by the user logged in
|
// This function is only used by the user logged in
|
||||||
// But it is used all the time.
|
// But it is used all the time.
|
||||||
val verifiedFollowKeySet: Set<HexKey> by lazy {
|
val verifiedFollowKeySet: Set<HexKey> by lazy {
|
||||||
tags.filter { it[0] == "p" }.mapNotNull {
|
tags.filter { it.size > 1 && it[0] == "p" }.mapNotNull {
|
||||||
it.getOrNull(1)?.let { unverifiedHex: String ->
|
try {
|
||||||
try {
|
decodePublicKey(it[1]).toHexKey()
|
||||||
decodePublicKey(unverifiedHex).toHexKey()
|
} catch (e: Exception) {
|
||||||
} catch (e: Exception) {
|
Log.w("ContactListEvent", "Can't parse tags as a follows: ${it[1]}", e)
|
||||||
Log.w("ContactListEvent", "Can't parse tags as a follows: ${it[1]}", e)
|
null
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.toSet()
|
}.toSet()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.actions
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.parseDirtyWordForKey
|
import com.vitorpamplona.amethyst.model.parseDirtyWordForKey
|
||||||
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
||||||
|
|
||||||
class NewMessageTagger(var channel: Channel?, var mentions: List<User>?, var replyTos: List<Note>?, var message: String) {
|
class NewMessageTagger(var channelHex: String?, var mentions: List<User>?, var replyTos: List<Note>?, var message: String) {
|
||||||
|
|
||||||
fun addUserToMentions(user: User) {
|
fun addUserToMentions(user: User) {
|
||||||
mentions = if (mentions?.contains(user) == true) mentions else mentions?.plus(user) ?: listOf(user)
|
mentions = if (mentions?.contains(user) == true) mentions else mentions?.plus(user) ?: listOf(user)
|
||||||
@@ -20,12 +19,12 @@ class NewMessageTagger(var channel: Channel?, var mentions: List<User>?, var rep
|
|||||||
|
|
||||||
fun tagIndex(user: User): Int {
|
fun tagIndex(user: User): Int {
|
||||||
// Postr Events assembles replies before mentions in the tag order
|
// Postr Events assembles replies before mentions in the tag order
|
||||||
return (if (channel != null) 1 else 0) + (replyTos?.size ?: 0) + (mentions?.indexOf(user) ?: 0)
|
return (if (channelHex != null) 1 else 0) + (replyTos?.size ?: 0) + (mentions?.indexOf(user) ?: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tagIndex(note: Note): Int {
|
fun tagIndex(note: Note): Int {
|
||||||
// Postr Events assembles replies before mentions in the tag order
|
// Postr Events assembles replies before mentions in the tag order
|
||||||
return (if (channel != null) 1 else 0) + (replyTos?.indexOf(note) ?: 0)
|
return (if (channelHex != null) 1 else 0) + (replyTos?.indexOf(note) ?: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun run() {
|
fun run() {
|
||||||
|
|||||||
@@ -1,197 +0,0 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.actions
|
|
||||||
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material.*
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
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.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, accountViewModel: AccountViewModel) {
|
|
||||||
val pollViewModel: NewPostViewModel = viewModel()
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
pollViewModel.load(accountViewModel.account, baseReplyTo, quote)
|
|
||||||
delay(100)
|
|
||||||
|
|
||||||
pollViewModel.imageUploadingError.collect { error ->
|
|
||||||
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialog(
|
|
||||||
onDismissRequest = { onClose() },
|
|
||||||
properties = DialogProperties(
|
|
||||||
usePlatformDefaultWidth = false,
|
|
||||||
dismissOnClickOutside = false,
|
|
||||||
decorFitsSystemWindows = false
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.fillMaxHeight()
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.fillMaxHeight()
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 10.dp, end = 10.dp, top = 10.dp)
|
|
||||||
.imePadding()
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
CloseButton(onCancel = {
|
|
||||||
pollViewModel.cancel()
|
|
||||||
onClose()
|
|
||||||
})
|
|
||||||
|
|
||||||
PollButton(
|
|
||||||
onPost = {
|
|
||||||
pollViewModel.sendPost()
|
|
||||||
onClose()
|
|
||||||
},
|
|
||||||
isActive = pollViewModel.message.text.isNotBlank() &&
|
|
||||||
pollViewModel.pollOptions.values.all { it.isNotEmpty() } &&
|
|
||||||
pollViewModel.isValidRecipients.value &&
|
|
||||||
pollViewModel.isValidvalueMaximum.value &&
|
|
||||||
pollViewModel.isValidvalueMinimum.value &&
|
|
||||||
pollViewModel.isValidConsensusThreshold.value &&
|
|
||||||
pollViewModel.isValidClosedAt.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.verticalScroll(scrollState)
|
|
||||||
) {
|
|
||||||
if (pollViewModel.replyTos != null && baseReplyTo?.event is TextNoteEvent) {
|
|
||||||
ReplyInformation(pollViewModel.replyTos, pollViewModel.mentions, accountViewModel, "✖ ") {
|
|
||||||
pollViewModel.removeFromReplyList(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(stringResource(R.string.poll_heading_required))
|
|
||||||
// NewPollRecipientsField(pollViewModel, account)
|
|
||||||
NewPollPrimaryDescription(pollViewModel)
|
|
||||||
pollViewModel.pollOptions.values.forEachIndexed { index, _ ->
|
|
||||||
NewPollOption(pollViewModel, index)
|
|
||||||
}
|
|
||||||
Button(
|
|
||||||
onClick = { pollViewModel.pollOptions[pollViewModel.pollOptions.size] = "" },
|
|
||||||
border = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.32f)),
|
|
||||||
colors = ButtonDefaults.outlinedButtonColors(
|
|
||||||
contentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painterResource(id = android.R.drawable.ic_input_add),
|
|
||||||
contentDescription = "Add poll option button",
|
|
||||||
modifier = Modifier.size(18.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(stringResource(R.string.poll_heading_optional))
|
|
||||||
NewPollVoteValueRange(pollViewModel)
|
|
||||||
NewPollConsensusThreshold(pollViewModel)
|
|
||||||
NewPollClosing(pollViewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val userSuggestions = pollViewModel.userSuggestions
|
|
||||||
if (userSuggestions.isNotEmpty()) {
|
|
||||||
LazyColumn(
|
|
||||||
contentPadding = PaddingValues(
|
|
||||||
top = 10.dp
|
|
||||||
),
|
|
||||||
modifier = Modifier.heightIn(0.dp, 300.dp)
|
|
||||||
) {
|
|
||||||
itemsIndexed(
|
|
||||||
userSuggestions,
|
|
||||||
key = { _, item -> item.pubkeyHex }
|
|
||||||
) { _, item ->
|
|
||||||
UserLine(item, accountViewModel) {
|
|
||||||
pollViewModel.autocompleteWithUser(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
|
||||||
/*UploadFromGallery(
|
|
||||||
isUploading = pollViewModel.isUploadingImage
|
|
||||||
) {
|
|
||||||
pollViewModel.upload(it, context)
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PollButton(modifier: Modifier = Modifier, onPost: () -> Unit = {}, isActive: Boolean) {
|
|
||||||
Button(
|
|
||||||
modifier = modifier,
|
|
||||||
onClick = {
|
|
||||||
if (isActive) {
|
|
||||||
onPost()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
shape = RoundedCornerShape(20.dp),
|
|
||||||
colors = ButtonDefaults
|
|
||||||
.buttonColors(
|
|
||||||
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.post_poll), color = Color.White)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*@Preview
|
|
||||||
@Composable
|
|
||||||
fun NewPollViewPreview() {
|
|
||||||
NewPollView(onClose = {}, account = Account(loggedIn = Persona()))
|
|
||||||
}*/
|
|
||||||
@@ -102,7 +102,7 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canAddInvoice = account.userProfile().info?.lnAddress() != null
|
canAddInvoice = account.userProfile().info?.lnAddress() != null
|
||||||
canUsePoll = originalNote?.event !is PrivateDmEvent && originalNote?.channel() == null
|
canUsePoll = originalNote?.event !is PrivateDmEvent && originalNote?.channelHex() == null
|
||||||
contentToAddUrl = null
|
contentToAddUrl = null
|
||||||
|
|
||||||
wantsForwardZapTo = false
|
wantsForwardZapTo = false
|
||||||
@@ -114,7 +114,7 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun sendPost() {
|
fun sendPost() {
|
||||||
val tagger = NewMessageTagger(originalNote?.channel(), mentions, replyTos, message.text)
|
val tagger = NewMessageTagger(originalNote?.channelHex(), mentions, replyTos, message.text)
|
||||||
tagger.run()
|
tagger.run()
|
||||||
|
|
||||||
val zapReceiver = if (wantsForwardZapTo) {
|
val zapReceiver = if (wantsForwardZapTo) {
|
||||||
@@ -129,8 +129,8 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
|
|
||||||
if (wantsPoll) {
|
if (wantsPoll) {
|
||||||
account?.sendPoll(tagger.message, tagger.replyTos, tagger.mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt, zapReceiver, wantsToMarkAsSensitive)
|
account?.sendPoll(tagger.message, tagger.replyTos, tagger.mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt, zapReceiver, wantsToMarkAsSensitive)
|
||||||
} else if (originalNote?.channel() != null) {
|
} else if (originalNote?.channelHex() != null) {
|
||||||
account?.sendChannelMessage(tagger.message, tagger.channel!!.idHex, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive)
|
account?.sendChannelMessage(tagger.message, tagger.channelHex!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive)
|
||||||
} else if (originalNote?.event is PrivateDmEvent) {
|
} else if (originalNote?.event is PrivateDmEvent) {
|
||||||
account?.sendPrivateMessage(tagger.message, originalNote!!.author!!.pubkeyHex, originalNote!!, tagger.mentions, zapReceiver, wantsToMarkAsSensitive)
|
account?.sendPrivateMessage(tagger.message, originalNote!!.author!!.pubkeyHex, originalNote!!, tagger.mentions, zapReceiver, wantsToMarkAsSensitive)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ 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.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -45,9 +46,9 @@ import com.vitorpamplona.amethyst.service.NIP30Parser
|
|||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
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 kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ClickableRoute(
|
fun ClickableRoute(
|
||||||
@@ -77,40 +78,52 @@ private fun DisplayEvent(
|
|||||||
var noteBase by remember(nip19) { mutableStateOf<Note?>(null) }
|
var noteBase by remember(nip19) { mutableStateOf<Note?>(null) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = nip19.hex) {
|
LaunchedEffect(key1 = nip19.hex) {
|
||||||
withContext(Dispatchers.IO) {
|
if (noteBase == null) {
|
||||||
noteBase = LocalCache.checkGetOrCreateNote(nip19.hex)
|
launch(Dispatchers.IO) {
|
||||||
|
noteBase = LocalCache.checkGetOrCreateNote(nip19.hex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
noteBase?.let {
|
noteBase?.let {
|
||||||
val noteState by it.live().metadata.observeAsState()
|
val noteState by it.live().metadata.observeAsState()
|
||||||
val note = remember(noteState) { noteState?.note } ?: return
|
val note = remember(noteState) { noteState?.note } ?: return
|
||||||
val channel = remember(noteState) { note.channel() }
|
val channelHex = remember(noteState) { note.channelHex() }
|
||||||
|
val noteIdDisplayNote = remember(noteState) { "@${note.idDisplayNote()}" }
|
||||||
|
|
||||||
if (note.event is ChannelCreateEvent) {
|
if (note.event is ChannelCreateEvent) {
|
||||||
CreateClickableText(
|
CreateClickableText(
|
||||||
clickablePart = "@${note.idDisplayNote()}",
|
clickablePart = noteIdDisplayNote,
|
||||||
suffix = "${nip19.additionalChars} ",
|
suffix = "${nip19.additionalChars} ",
|
||||||
route = "Channel/${nip19.hex}",
|
route = "Channel/${nip19.hex}",
|
||||||
nav = nav
|
nav = nav
|
||||||
)
|
)
|
||||||
} else if (note.event is PrivateDmEvent) {
|
} else if (note.event is PrivateDmEvent) {
|
||||||
CreateClickableText(
|
CreateClickableText(
|
||||||
clickablePart = "@${note.idDisplayNote()}",
|
clickablePart = noteIdDisplayNote,
|
||||||
suffix = "${nip19.additionalChars} ",
|
suffix = "${nip19.additionalChars} ",
|
||||||
route = "Room/${note.author?.pubkeyHex}",
|
route = "Room/${note.author?.pubkeyHex}",
|
||||||
nav = nav
|
nav = nav
|
||||||
)
|
)
|
||||||
} else if (channel != null) {
|
} else if (channelHex != null) {
|
||||||
CreateClickableText(
|
LoadChannel(baseChannelHex = channelHex) { baseChannel ->
|
||||||
clickablePart = channel.toBestDisplayName(),
|
val channelState by baseChannel.live.observeAsState()
|
||||||
suffix = "${nip19.additionalChars} ",
|
val channelDisplayName by remember(channelState) {
|
||||||
route = "Channel/${channel.idHex}",
|
derivedStateOf {
|
||||||
nav = nav
|
channelState?.channel?.toBestDisplayName() ?: noteIdDisplayNote
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateClickableText(
|
||||||
|
clickablePart = channelDisplayName,
|
||||||
|
suffix = "${nip19.additionalChars} ",
|
||||||
|
route = "Channel/${baseChannel.idHex}",
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
CreateClickableText(
|
CreateClickableText(
|
||||||
clickablePart = "@${note.idDisplayNote()}",
|
clickablePart = noteIdDisplayNote,
|
||||||
suffix = "${nip19.additionalChars} ",
|
suffix = "${nip19.additionalChars} ",
|
||||||
route = "Event/${nip19.hex}",
|
route = "Event/${nip19.hex}",
|
||||||
nav = nav
|
nav = nav
|
||||||
@@ -141,32 +154,42 @@ private fun DisplayNote(
|
|||||||
noteBase?.let {
|
noteBase?.let {
|
||||||
val noteState by it.live().metadata.observeAsState()
|
val noteState by it.live().metadata.observeAsState()
|
||||||
val note = remember(noteState) { noteState?.note } ?: return
|
val note = remember(noteState) { noteState?.note } ?: return
|
||||||
val channel = note.channel()
|
val channelHex = note.channelHex()
|
||||||
|
val noteIdDisplayNote = remember(noteState) { "@${note.idDisplayNote()}" }
|
||||||
|
|
||||||
if (note.event is ChannelCreateEvent) {
|
if (note.event is ChannelCreateEvent) {
|
||||||
CreateClickableText(
|
CreateClickableText(
|
||||||
clickablePart = "@${note.idDisplayNote()}",
|
clickablePart = noteIdDisplayNote,
|
||||||
suffix = "${nip19.additionalChars} ",
|
suffix = "${nip19.additionalChars} ",
|
||||||
route = "Channel/${nip19.hex}",
|
route = "Channel/${nip19.hex}",
|
||||||
nav = nav
|
nav = nav
|
||||||
)
|
)
|
||||||
} else if (note.event is PrivateDmEvent) {
|
} else if (note.event is PrivateDmEvent) {
|
||||||
CreateClickableText(
|
CreateClickableText(
|
||||||
clickablePart = "@${note.idDisplayNote()}",
|
clickablePart = noteIdDisplayNote,
|
||||||
suffix = "${nip19.additionalChars} ",
|
suffix = "${nip19.additionalChars} ",
|
||||||
route = "Room/${note.author?.pubkeyHex}",
|
route = "Room/${note.author?.pubkeyHex}",
|
||||||
nav = nav
|
nav = nav
|
||||||
)
|
)
|
||||||
} else if (channel != null) {
|
} else if (channelHex != null) {
|
||||||
CreateClickableText(
|
LoadChannel(baseChannelHex = channelHex) { baseChannel ->
|
||||||
clickablePart = channel.toBestDisplayName(),
|
val channelState by baseChannel.live.observeAsState()
|
||||||
suffix = "${nip19.additionalChars} ",
|
val channelDisplayName by remember(channelState) {
|
||||||
route = "Channel/${note.channel()?.idHex}",
|
derivedStateOf {
|
||||||
nav = nav
|
channelState?.channel?.toBestDisplayName() ?: noteIdDisplayNote
|
||||||
)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateClickableText(
|
||||||
|
clickablePart = channelDisplayName,
|
||||||
|
suffix = "${nip19.additionalChars} ",
|
||||||
|
route = "Channel/${baseChannel.idHex}",
|
||||||
|
nav = nav
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
CreateClickableText(
|
CreateClickableText(
|
||||||
clickablePart = "@${note.idDisplayNote()}",
|
clickablePart = noteIdDisplayNote,
|
||||||
suffix = "${nip19.additionalChars} ",
|
suffix = "${nip19.additionalChars} ",
|
||||||
route = "Note/${nip19.hex}",
|
route = "Note/${nip19.hex}",
|
||||||
nav = nav
|
nav = nav
|
||||||
@@ -434,7 +457,9 @@ fun ClickableInLineIconRenderer(wordsInOrder: List<Renderable>, style: SpanStyle
|
|||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = value.url,
|
model = value.url,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.fillMaxSize().padding(1.dp)
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(1.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -498,7 +523,9 @@ fun InLineIconRenderer(
|
|||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = value.url,
|
model = value.url,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.fillMaxSize().padding(horizontal = 1.dp)
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = 1.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -228,7 +228,6 @@ fun AppNavigation(
|
|||||||
composable(route.route, route.arguments, content = {
|
composable(route.route, route.arguments, content = {
|
||||||
LoadRedirectScreen(
|
LoadRedirectScreen(
|
||||||
eventId = it.arguments?.getString("id"),
|
eventId = it.arguments?.getString("id"),
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
navController = navController
|
navController = navController
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ 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.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -66,39 +67,59 @@ fun ChatroomCompose(
|
|||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val channelHex by remember(noteState) {
|
||||||
|
derivedStateOf {
|
||||||
|
noteState?.note?.channelHex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (note?.event == null) {
|
if (note?.event == null) {
|
||||||
BlankNote(Modifier)
|
BlankNote(Modifier)
|
||||||
} else if (note.channel() != null) {
|
} else if (channelHex != null) {
|
||||||
val authorState by note.author!!.live().metadata.observeAsState()
|
LoadChannel(baseChannelHex = channelHex!!) { channel ->
|
||||||
val author = authorState?.user
|
val authorState by note.author!!.live().metadata.observeAsState()
|
||||||
|
val authorName = remember(authorState) {
|
||||||
|
authorState?.user?.toBestDisplayName()
|
||||||
|
}
|
||||||
|
|
||||||
val channelState by note.channel()!!.live.observeAsState()
|
val chanHex = remember { channel.idHex }
|
||||||
val channel = channelState?.channel
|
|
||||||
|
|
||||||
val noteEvent = note.event
|
val channelState by channel.live.observeAsState()
|
||||||
|
val channelPicture by remember(channelState) {
|
||||||
|
derivedStateOf {
|
||||||
|
channel.profilePicture()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val channelName by remember(channelState) {
|
||||||
|
derivedStateOf {
|
||||||
|
channel.info.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val noteEvent = note.event
|
||||||
|
|
||||||
|
val description = if (noteEvent is ChannelCreateEvent) {
|
||||||
|
stringResource(R.string.channel_created)
|
||||||
|
} else if (noteEvent is ChannelMetadataEvent) {
|
||||||
|
"${stringResource(R.string.channel_information_changed_to)} "
|
||||||
|
} else {
|
||||||
|
noteEvent?.content()
|
||||||
|
}
|
||||||
|
|
||||||
val description = if (noteEvent is ChannelCreateEvent) {
|
|
||||||
stringResource(R.string.channel_created)
|
|
||||||
} else if (noteEvent is ChannelMetadataEvent) {
|
|
||||||
"${stringResource(R.string.channel_information_changed_to)} "
|
|
||||||
} else {
|
|
||||||
noteEvent?.content()
|
|
||||||
}
|
|
||||||
channel?.let { chan ->
|
|
||||||
var hasNewMessages by remember { mutableStateOf<Boolean>(false) }
|
var hasNewMessages by remember { mutableStateOf<Boolean>(false) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = notificationCache, key2 = note) {
|
LaunchedEffect(key1 = notificationCache, key2 = note) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
note.createdAt()?.let { timestamp ->
|
note.createdAt()?.let { timestamp ->
|
||||||
hasNewMessages =
|
hasNewMessages =
|
||||||
timestamp > notificationCache.cache.load("Channel/${chan.idHex}")
|
timestamp > notificationCache.cache.load("Channel/$chanHex")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChannelName(
|
ChannelName(
|
||||||
channelIdHex = chan.idHex,
|
channelIdHex = chanHex,
|
||||||
channelPicture = chan.profilePicture(),
|
channelPicture = channelPicture,
|
||||||
channelTitle = { modifier ->
|
channelTitle = { modifier ->
|
||||||
Text(
|
Text(
|
||||||
text = buildAnnotatedString {
|
text = buildAnnotatedString {
|
||||||
@@ -107,7 +128,7 @@ fun ChatroomCompose(
|
|||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
append(chan.info.name)
|
append(channelName)
|
||||||
}
|
}
|
||||||
|
|
||||||
withStyle(
|
withStyle(
|
||||||
@@ -125,9 +146,9 @@ fun ChatroomCompose(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
channelLastTime = note.createdAt(),
|
channelLastTime = note.createdAt(),
|
||||||
channelLastContent = "${author?.toBestDisplayName()}: " + description,
|
channelLastContent = "$authorName: $description",
|
||||||
hasNewMessages = hasNewMessages,
|
hasNewMessages = hasNewMessages,
|
||||||
onClick = { nav("Channel/${chan.idHex}") }
|
onClick = { nav("Channel/$chanHex") }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ fun ChatroomMessageCompose(
|
|||||||
var isAcceptableAndCanPreview by remember { mutableStateOf(Pair(true, true)) }
|
var isAcceptableAndCanPreview by remember { mutableStateOf(Pair(true, true)) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = noteReportsState, key2 = accountState) {
|
LaunchedEffect(key1 = noteReportsState, key2 = accountState) {
|
||||||
withContext(Dispatchers.IO) {
|
launch(Dispatchers.Default) {
|
||||||
account.userProfile().let { loggedIn ->
|
account.userProfile().let { loggedIn ->
|
||||||
val newCanPreview = note.author?.pubkeyHex == loggedIn.pubkeyHex ||
|
val newCanPreview = note.author?.pubkeyHex == loggedIn.pubkeyHex ||
|
||||||
(note.author?.let { loggedIn.isFollowingCached(it) } ?: true) ||
|
(note.author?.let { loggedIn.isFollowingCached(it) } ?: true) ||
|
||||||
|
|||||||
@@ -138,6 +138,8 @@ import java.io.File
|
|||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
import kotlin.time.measureTimedValue
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -154,10 +156,12 @@ fun NoteCompose(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
|
println("NormalNote START NoteCompose")
|
||||||
val noteState by baseNote.live().metadata.observeAsState()
|
val noteState by baseNote.live().metadata.observeAsState()
|
||||||
val noteEvent = remember(noteState) { noteState?.note?.event }
|
val noteEvent = remember(noteState) { noteState?.note?.event }
|
||||||
|
|
||||||
if (noteEvent == null) {
|
if (noteEvent == null) {
|
||||||
|
println("NormalNote Rendering Blank")
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
BlankNote(
|
BlankNote(
|
||||||
@@ -186,6 +190,7 @@ fun NoteCompose(
|
|||||||
nav
|
nav
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
println("NormalNote END NoteCompose")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -251,32 +256,24 @@ fun LoadedNoteCompose(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
var state by remember {
|
||||||
val account = remember(accountState) { accountState?.account } ?: return
|
mutableStateOf(
|
||||||
|
NoteComposeReportState(
|
||||||
|
isAcceptable = true,
|
||||||
|
canPreview = true,
|
||||||
|
relevantReports = emptySet()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val noteReportsState by note.live().reports.observeAsState()
|
WatchForReports(note, accountViewModel) { newIsAcceptable, newCanPreview, newRelevantReports ->
|
||||||
val noteForReports = remember(noteReportsState) { noteReportsState?.note } ?: return
|
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
|
||||||
|
state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports)
|
||||||
var showReportedNote by remember { mutableStateOf(false) }
|
|
||||||
var state by remember { mutableStateOf(NoteComposeReportState(true, true, emptySet())) }
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = noteReportsState, key2 = accountState) {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
account.userProfile().let { loggedIn ->
|
|
||||||
val newCanPreview = note.author?.pubkeyHex == loggedIn.pubkeyHex ||
|
|
||||||
(note.author?.let { loggedIn.isFollowingCached(it) } ?: true) ||
|
|
||||||
!(noteForReports.hasAnyReports())
|
|
||||||
|
|
||||||
val newIsAcceptable = account.isAcceptable(noteForReports)
|
|
||||||
val newRelevantReports = account.getRelevantReports(noteForReports)
|
|
||||||
|
|
||||||
if (newIsAcceptable != state.isAcceptable || newCanPreview != state.canPreview) {
|
|
||||||
state = NoteComposeReportState(newIsAcceptable, newCanPreview, newRelevantReports)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showReportedNote by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val showHiddenNote by remember(state, showReportedNote) {
|
val showHiddenNote by remember(state, showReportedNote) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
!state.isAcceptable && !showReportedNote
|
!state.isAcceptable && !showReportedNote
|
||||||
@@ -316,7 +313,35 @@ fun LoadedNoteCompose(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@Composable
|
||||||
|
private fun WatchForReports(
|
||||||
|
note: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
onChange: (Boolean, Boolean, Set<Note>) -> Unit
|
||||||
|
) {
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val account = remember(accountState) { accountState?.account } ?: return
|
||||||
|
|
||||||
|
val noteReportsState by note.live().reports.observeAsState()
|
||||||
|
val noteForReports = remember(noteReportsState) { noteReportsState?.note } ?: return
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = noteReportsState, key2 = accountState) {
|
||||||
|
launch(Dispatchers.Default) {
|
||||||
|
account.userProfile().let { loggedIn ->
|
||||||
|
val newCanPreview = note.author?.pubkeyHex == loggedIn.pubkeyHex ||
|
||||||
|
(note.author?.let { loggedIn.isFollowingCached(it) } ?: true) ||
|
||||||
|
!(noteForReports.hasAnyReports())
|
||||||
|
|
||||||
|
val newIsAcceptable = account.isAcceptable(noteForReports)
|
||||||
|
val newRelevantReports = account.getRelevantReports(noteForReports)
|
||||||
|
|
||||||
|
onChange(newIsAcceptable, newCanPreview, newRelevantReports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalTime::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun NormalNote(
|
fun NormalNote(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
@@ -346,6 +371,8 @@ fun NormalNote(
|
|||||||
} else if (noteEvent is FileStorageHeaderEvent) {
|
} else if (noteEvent is FileStorageHeaderEvent) {
|
||||||
FileStorageHeaderDisplay(baseNote)
|
FileStorageHeaderDisplay(baseNote)
|
||||||
} else {
|
} else {
|
||||||
|
println("NormalNote LoadedNoteCompose")
|
||||||
|
|
||||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -368,6 +395,8 @@ fun NormalNote(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println("NormalNote LoadedNoteCompose Launched Effect")
|
||||||
|
|
||||||
val primaryColor = MaterialTheme.colors.newItemBackgroundColor
|
val primaryColor = MaterialTheme.colors.newItemBackgroundColor
|
||||||
val defaultBackgroundColor = MaterialTheme.colors.background
|
val defaultBackgroundColor = MaterialTheme.colors.background
|
||||||
|
|
||||||
@@ -383,6 +412,8 @@ fun NormalNote(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println("NormalNote LoadedNoteCompose Background Color")
|
||||||
|
|
||||||
val columnModifier = remember(backgroundColor) {
|
val columnModifier = remember(backgroundColor) {
|
||||||
modifier
|
modifier
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
@@ -398,6 +429,8 @@ fun NormalNote(
|
|||||||
.background(backgroundColor)
|
.background(backgroundColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println("NormalNote LoadedNoteCompose Modifier")
|
||||||
|
|
||||||
Column(modifier = columnModifier) {
|
Column(modifier = columnModifier) {
|
||||||
Row(
|
Row(
|
||||||
modifier = remember {
|
modifier = remember {
|
||||||
@@ -409,9 +442,14 @@ fun NormalNote(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (!isBoostedNote && !isQuotedNote) {
|
println("NormalNote Before FirstUserInfo")
|
||||||
DrawAuthorImages(baseNote, accountViewModel, nav)
|
val (value, elapsed) = measureTimedValue {
|
||||||
|
if (!isBoostedNote && !isQuotedNote) {
|
||||||
|
DrawAuthorImages(baseNote, accountViewModel, nav)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
println("AAA $elapsed DrawAuthorImages")
|
||||||
|
println("NormalNote FirstUserInfo")
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = remember {
|
modifier = remember {
|
||||||
@@ -433,6 +471,7 @@ fun NormalNote(
|
|||||||
nav
|
nav
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
println("NormalNote SecondUserInfo")
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
|
||||||
@@ -521,8 +560,8 @@ fun routeFor(note: Note, loggedIn: User): String? {
|
|||||||
val noteEvent = note.event
|
val noteEvent = note.event
|
||||||
|
|
||||||
if (noteEvent is ChannelMessageEvent || noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) {
|
if (noteEvent is ChannelMessageEvent || noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) {
|
||||||
note.channel()?.let {
|
note.channelHex()?.let {
|
||||||
return "Channel/${it.idHex}"
|
return "Channel/$it"
|
||||||
}
|
}
|
||||||
} else if (noteEvent is PrivateDmEvent) {
|
} else if (noteEvent is PrivateDmEvent) {
|
||||||
return "Room/${noteEvent.talkingWith(loggedIn.pubkeyHex)}"
|
return "Room/${noteEvent.talkingWith(loggedIn.pubkeyHex)}"
|
||||||
@@ -546,6 +585,7 @@ private fun RenderTextEvent(
|
|||||||
|
|
||||||
if (eventContent != null) {
|
if (eventContent != null) {
|
||||||
val isAuthorTheLoggedUser = remember(note.event) { accountViewModel.isLoggedUser(note.author) }
|
val isAuthorTheLoggedUser = remember(note.event) { accountViewModel.isLoggedUser(note.author) }
|
||||||
|
|
||||||
if (makeItShort && isAuthorTheLoggedUser) {
|
if (makeItShort && isAuthorTheLoggedUser) {
|
||||||
Text(
|
Text(
|
||||||
text = eventContent,
|
text = eventContent,
|
||||||
@@ -1232,7 +1272,7 @@ private fun ReplyRow(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(5.dp))
|
Spacer(modifier = Modifier.height(5.dp))
|
||||||
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
|
} else if (noteEvent is ChannelMessageEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
|
||||||
note.channel()?.let {
|
note.channelHex()?.let {
|
||||||
ReplyInformationChannel(note.replyTo, noteEvent.mentions(), it, accountViewModel, nav)
|
ReplyInformationChannel(note.replyTo, noteEvent.mentions(), it, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1341,23 +1381,36 @@ fun TimeAgo(time: Long) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun DrawAuthorImages(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
private fun DrawAuthorImages(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
val baseChannel = remember { baseNote.channel() }
|
val baseChannelHex = remember { baseNote.channelHex() }
|
||||||
val modifier = remember { Modifier.width(55.dp) }
|
val modifier = remember { Modifier.width(55.dp) }
|
||||||
|
|
||||||
Column(modifier) {
|
Column(modifier) {
|
||||||
// Draws the boosted picture outside the boosted card.
|
// Draws the boosted picture outside the boosted card.
|
||||||
Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) {
|
Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) {
|
||||||
NoteAuthorPicture(baseNote, nav, accountViewModel, 55.dp)
|
val (value1, elapsed1) = measureTimedValue {
|
||||||
|
NoteAuthorPicture(baseNote, nav, accountViewModel, 55.dp)
|
||||||
if (baseNote.event is RepostEvent) {
|
|
||||||
RepostNoteAuthorPicture(baseNote, accountViewModel, nav)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseNote.event is ChannelMessageEvent && baseChannel != null) {
|
println("AAA $elapsed1 NoteAuthorPicture")
|
||||||
ChannelNotePicture(baseChannel)
|
|
||||||
|
val (value2, elapsed2) = measureTimedValue {
|
||||||
|
if (baseNote.event is RepostEvent) {
|
||||||
|
RepostNoteAuthorPicture(baseNote, accountViewModel, nav)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
println("AAA $elapsed2 RepostNoteAuthorPicture")
|
||||||
|
|
||||||
|
val (value3, elapsed3) = measureTimedValue {
|
||||||
|
if (baseNote.event is ChannelMessageEvent && baseChannelHex != null) {
|
||||||
|
LoadChannel(baseChannelHex) { channel ->
|
||||||
|
ChannelNotePicture(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("AAA $elapsed3 ChannelNotePicture")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseNote.event is RepostEvent) {
|
if (baseNote.event is RepostEvent) {
|
||||||
@@ -1373,10 +1426,29 @@ private fun DrawAuthorImages(baseNote: Note, accountViewModel: AccountViewModel,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadChannel(baseChannelHex: String, content: @Composable (Channel) -> Unit) {
|
||||||
|
var channel by remember(baseChannelHex) {
|
||||||
|
mutableStateOf<Channel?>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = baseChannelHex) {
|
||||||
|
if (channel == null) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
channel = LocalCache.checkGetOrCreateChannel(baseChannelHex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channel?.let {
|
||||||
|
content(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChannelNotePicture(baseChannel: Channel) {
|
private fun ChannelNotePicture(baseChannel: Channel) {
|
||||||
val channelState by baseChannel.live.observeAsState()
|
val channelState by baseChannel.live.observeAsState()
|
||||||
val channel = channelState?.channel
|
val channel = remember(channelState) { channelState?.channel } ?: return
|
||||||
|
|
||||||
val modifier = remember {
|
val modifier = remember {
|
||||||
Modifier
|
Modifier
|
||||||
@@ -1391,25 +1463,23 @@ private fun ChannelNotePicture(baseChannel: Channel) {
|
|||||||
.height(30.dp)
|
.height(30.dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channel != null) {
|
val model = remember(channelState) {
|
||||||
val model = remember(channelState) {
|
ResizeImage(channel.profilePicture(), 30.dp)
|
||||||
ResizeImage(channel.profilePicture(), 30.dp)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Box(boxModifier) {
|
Box(boxModifier) {
|
||||||
RobohashAsyncImageProxy(
|
RobohashAsyncImageProxy(
|
||||||
robot = channel.idHex,
|
robot = channel.idHex,
|
||||||
model = model,
|
model = model,
|
||||||
contentDescription = stringResource(R.string.group_picture),
|
contentDescription = stringResource(R.string.group_picture),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(MaterialTheme.colors.background)
|
.background(MaterialTheme.colors.background)
|
||||||
.border(
|
.border(
|
||||||
2.dp,
|
2.dp,
|
||||||
MaterialTheme.colors.background,
|
MaterialTheme.colors.background,
|
||||||
CircleShape
|
CircleShape
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2153,8 +2223,10 @@ fun NoteAuthorPicture(
|
|||||||
onClick: ((User) -> Unit)? = null
|
onClick: ((User) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val noteState by baseNote.live().metadata.observeAsState()
|
val noteState by baseNote.live().metadata.observeAsState()
|
||||||
val author = remember(noteState) {
|
val author by remember(noteState) {
|
||||||
noteState?.note?.author
|
derivedStateOf {
|
||||||
|
noteState?.note?.author
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val boxModifier = remember {
|
val boxModifier = remember {
|
||||||
@@ -2179,7 +2251,7 @@ fun NoteAuthorPicture(
|
|||||||
modifier = nullModifier.background(MaterialTheme.colors.background)
|
modifier = nullModifier.background(MaterialTheme.colors.background)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
UserPicture(author, size, accountViewModel, modifier, onClick)
|
UserPicture(author!!, size, accountViewModel, modifier, onClick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2253,15 +2325,12 @@ fun UserPicture(
|
|||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val myBoxModifier = remember {
|
val myBoxModifier = remember {
|
||||||
Modifier
|
Modifier.size(size)
|
||||||
.width(size)
|
|
||||||
.height(size)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val myImageModifier = remember {
|
val myImageModifier = remember {
|
||||||
modifier
|
modifier
|
||||||
.width(size)
|
.size(size)
|
||||||
.height(size)
|
|
||||||
.clip(shape = CircleShape)
|
.clip(shape = CircleShape)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2283,10 +2352,17 @@ fun UserPicture(
|
|||||||
private fun ObserveAndDisplayFollowingMark(userHex: String, iconSize: Dp, accountViewModel: AccountViewModel) {
|
private fun ObserveAndDisplayFollowingMark(userHex: String, iconSize: Dp, accountViewModel: AccountViewModel) {
|
||||||
val accountFollowsState by accountViewModel.account.userProfile().live().follows.observeAsState()
|
val accountFollowsState by accountViewModel.account.userProfile().live().follows.observeAsState()
|
||||||
|
|
||||||
val showFollowingMark by remember(accountFollowsState) {
|
var showFollowingMark by remember { mutableStateOf(false) }
|
||||||
derivedStateOf {
|
|
||||||
accountFollowsState?.user?.isFollowingCached(userHex) == true ||
|
LaunchedEffect(key1 = accountFollowsState) {
|
||||||
(userHex == accountViewModel.account.userProfile().pubkeyHex)
|
launch(Dispatchers.Default) {
|
||||||
|
val newShowFollowingMark =
|
||||||
|
accountFollowsState?.user?.isFollowingCached(userHex) == true ||
|
||||||
|
(userHex == accountViewModel.account.userProfile().pubkeyHex)
|
||||||
|
|
||||||
|
if (newShowFollowingMark != showFollowingMark) {
|
||||||
|
showFollowingMark = newShowFollowingMark
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,33 +111,34 @@ private fun VerticalDivider(color: Color) =
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit, accountViewModel: AccountViewModel) {
|
fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit, accountViewModel: AccountViewModel) {
|
||||||
val context = LocalContext.current
|
|
||||||
val primaryLight = lightenColor(MaterialTheme.colors.primary, 0.1f)
|
|
||||||
val cardShape = RoundedCornerShape(5.dp)
|
|
||||||
val clipboardManager = LocalClipboardManager.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
var showSelectTextDialog by remember(note) { mutableStateOf(false) }
|
var showSelectTextDialog by remember(note) { mutableStateOf(false) }
|
||||||
var showDeleteAlertDialog by remember(note) { mutableStateOf(false) }
|
var showDeleteAlertDialog by remember(note) { mutableStateOf(false) }
|
||||||
var showBlockAlertDialog by remember(note) { mutableStateOf(false) }
|
var showBlockAlertDialog by remember(note) { mutableStateOf(false) }
|
||||||
var showReportDialog by remember(note) { mutableStateOf(false) }
|
var showReportDialog by remember(note) { mutableStateOf(false) }
|
||||||
|
|
||||||
val backgroundColor = if (MaterialTheme.colors.isLight) {
|
|
||||||
MaterialTheme.colors.primary
|
|
||||||
} else {
|
|
||||||
MaterialTheme.colors.primary.copy(alpha = 0.32f).compositeOver(MaterialTheme.colors.background)
|
|
||||||
}
|
|
||||||
|
|
||||||
val showToast = { stringResource: Int ->
|
|
||||||
scope.launch {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
context.getString(stringResource),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (popupExpanded) {
|
if (popupExpanded) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val primaryLight = lightenColor(MaterialTheme.colors.primary, 0.1f)
|
||||||
|
val cardShape = RoundedCornerShape(5.dp)
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val backgroundColor = if (MaterialTheme.colors.isLight) {
|
||||||
|
MaterialTheme.colors.primary
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colors.primary.copy(alpha = 0.32f).compositeOver(MaterialTheme.colors.background)
|
||||||
|
}
|
||||||
|
|
||||||
|
val showToast = { stringResource: Int ->
|
||||||
|
scope.launch {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(stringResource),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val isOwnNote = accountViewModel.isLoggedUser(note.author)
|
val isOwnNote = accountViewModel.isLoggedUser(note.author)
|
||||||
val isFollowingUser = !isOwnNote && accountViewModel.isFollowing(note.author)
|
val isFollowingUser = !isOwnNote && accountViewModel.isFollowing(note.author)
|
||||||
|
|
||||||
|
|||||||
@@ -29,36 +29,35 @@ fun ReplyInformation(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
var dupMentions by remember { mutableStateOf<List<User>?>(null) }
|
var sortedMentions by remember { mutableStateOf<List<User>?>(null) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
dupMentions = mentions.mapNotNull { LocalCache.checkGetOrCreateUser(it) }
|
sortedMentions = mentions.mapNotNull { LocalCache.checkGetOrCreateUser(it) }
|
||||||
|
?.toSet()?.sortedBy { !accountViewModel.account.userProfile().isFollowingCached(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dupMentions != null) {
|
if (sortedMentions != null) {
|
||||||
ReplyInformation(replyTo, dupMentions, accountViewModel) {
|
ReplyInformation(replyTo, sortedMentions) {
|
||||||
nav("User/${it.pubkeyHex}")
|
nav("User/${it.pubkeyHex}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ReplyInformation(
|
private fun ReplyInformation(
|
||||||
replyTo: List<Note>?,
|
replyTo: List<Note>?,
|
||||||
dupMentions: List<User>?,
|
sortedMentions: List<User>?,
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
prefix: String = "",
|
prefix: String = "",
|
||||||
onUserTagClick: (User) -> Unit
|
onUserTagClick: (User) -> Unit
|
||||||
) {
|
) {
|
||||||
val mentions = dupMentions?.toSet()?.sortedBy { !accountViewModel.account.userProfile().isFollowingCached(it) }
|
var expanded by remember { mutableStateOf((sortedMentions?.size ?: 0) <= 2) }
|
||||||
var expanded by remember { mutableStateOf((mentions?.size ?: 0) <= 2) }
|
|
||||||
|
|
||||||
FlowRow() {
|
FlowRow() {
|
||||||
if (mentions != null && mentions.isNotEmpty()) {
|
if (sortedMentions != null && sortedMentions.isNotEmpty()) {
|
||||||
if (replyTo != null && replyTo.isNotEmpty()) {
|
if (replyTo != null && replyTo.isNotEmpty()) {
|
||||||
val repliesToDisplay = if (expanded) mentions else mentions.take(2)
|
val repliesToDisplay = if (expanded) sortedMentions else sortedMentions.take(2)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.replying_to),
|
stringResource(R.string.replying_to),
|
||||||
@@ -98,7 +97,7 @@ fun ReplyInformation(
|
|||||||
)
|
)
|
||||||
|
|
||||||
ClickableText(
|
ClickableText(
|
||||||
AnnotatedString("${mentions.size - 2}"),
|
AnnotatedString("${sortedMentions.size - 2}"),
|
||||||
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(alpha = 0.52f), fontSize = 13.sp),
|
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary.copy(alpha = 0.52f), fontSize = 13.sp),
|
||||||
onClick = { expanded = true }
|
onClick = { expanded = true }
|
||||||
)
|
)
|
||||||
@@ -120,7 +119,7 @@ fun ReplyInformation(
|
|||||||
fun ReplyInformationChannel(
|
fun ReplyInformationChannel(
|
||||||
replyTo: List<Note>?,
|
replyTo: List<Note>?,
|
||||||
mentions: List<String>,
|
mentions: List<String>,
|
||||||
channel: Channel,
|
channelHex: String,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -136,17 +135,19 @@ fun ReplyInformationChannel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sortedMentions != null) {
|
if (sortedMentions != null) {
|
||||||
ReplyInformationChannel(
|
LoadChannel(channelHex) { channel ->
|
||||||
replyTo,
|
ReplyInformationChannel(
|
||||||
sortedMentions,
|
replyTo,
|
||||||
channel,
|
sortedMentions,
|
||||||
onUserTagClick = {
|
channel,
|
||||||
nav("User/${it.pubkeyHex}")
|
onUserTagClick = {
|
||||||
},
|
nav("User/${it.pubkeyHex}")
|
||||||
onChannelTagClick = {
|
},
|
||||||
nav("Channel/${it.idHex}")
|
onChannelTagClick = {
|
||||||
}
|
nav("Channel/${it.idHex}")
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ private fun FeedLoaded(
|
|||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account ?: return
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
val notificationCacheState = NotificationCache.live.observeAsState()
|
val notificationCacheState = NotificationCache.live.observeAsState()
|
||||||
val notificationCache = notificationCacheState.value ?: return
|
val notificationCache = notificationCacheState.value ?: return
|
||||||
|
|
||||||
@@ -99,9 +100,9 @@ private fun FeedLoaded(
|
|||||||
if (markAsRead.value) {
|
if (markAsRead.value) {
|
||||||
for (note in state.feed.value) {
|
for (note in state.feed.value) {
|
||||||
note.event?.let {
|
note.event?.let {
|
||||||
val channel = note.channel()
|
val channelHex = note.channelHex()
|
||||||
val route = if (channel != null) {
|
val route = if (channelHex != null) {
|
||||||
"Channel/${channel.idHex}"
|
"Channel/$channelHex"
|
||||||
} else {
|
} else {
|
||||||
val replyAuthorBase =
|
val replyAuthorBase =
|
||||||
(note.event as? PrivateDmEvent)
|
(note.event as? PrivateDmEvent)
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import androidx.compose.ui.unit.dp
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
import kotlin.time.measureTimedValue
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -143,6 +145,7 @@ private fun WatchScrollToTop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun FeedLoaded(
|
private fun FeedLoaded(
|
||||||
state: FeedState.Loaded,
|
state: FeedState.Loaded,
|
||||||
@@ -163,14 +166,17 @@ private fun FeedLoaded(
|
|||||||
state = listState
|
state = listState
|
||||||
) {
|
) {
|
||||||
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
|
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
|
||||||
NoteCompose(
|
val (value, elapsed) = measureTimedValue {
|
||||||
item,
|
NoteCompose(
|
||||||
routeForLastRead = routeForLastRead,
|
item,
|
||||||
modifier = baseModifier,
|
routeForLastRead = routeForLastRead,
|
||||||
isBoostedNote = false,
|
modifier = baseModifier,
|
||||||
accountViewModel = accountViewModel,
|
isBoostedNote = false,
|
||||||
nav = nav
|
accountViewModel = accountViewModel,
|
||||||
)
|
nav = nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
println("AAA NoteCompose $elapsed ${item.event?.content()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ fun ChannelScreen(
|
|||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
PostButton(
|
PostButton(
|
||||||
onPost = {
|
onPost = {
|
||||||
val tagger = NewMessageTagger(channel, listOfNotNull(replyTo.value?.author), listOfNotNull(replyTo.value), channelScreenModel.message.text)
|
val tagger = NewMessageTagger(channel.idHex, listOfNotNull(replyTo.value?.author), listOfNotNull(replyTo.value), channelScreenModel.message.text)
|
||||||
tagger.run()
|
tagger.run()
|
||||||
account.sendChannelMessage(tagger.message, channel.idHex, tagger.replyTos, tagger.mentions, wantsToMarkAsSensitive = false)
|
account.sendChannelMessage(tagger.message, channel.idHex, tagger.replyTos, tagger.mentions, wantsToMarkAsSensitive = false)
|
||||||
channelScreenModel.message = TextFieldValue("")
|
channelScreenModel.message = TextFieldValue("")
|
||||||
|
|||||||
+4
-4
@@ -21,7 +21,7 @@ import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
|||||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LoadRedirectScreen(eventId: String?, accountViewModel: AccountViewModel, navController: NavController) {
|
fun LoadRedirectScreen(eventId: String?, navController: NavController) {
|
||||||
if (eventId == null) return
|
if (eventId == null) return
|
||||||
|
|
||||||
val baseNote = LocalCache.checkGetOrCreateNote(eventId) ?: return
|
val baseNote = LocalCache.checkGetOrCreateNote(eventId) ?: return
|
||||||
@@ -31,7 +31,7 @@ fun LoadRedirectScreen(eventId: String?, accountViewModel: AccountViewModel, nav
|
|||||||
|
|
||||||
LaunchedEffect(key1 = noteState) {
|
LaunchedEffect(key1 = noteState) {
|
||||||
val event = note?.event
|
val event = note?.event
|
||||||
val channel = note?.channel()
|
val channelHex = note?.channelHex()
|
||||||
|
|
||||||
if (event == null) {
|
if (event == null) {
|
||||||
// stay here, loading
|
// stay here, loading
|
||||||
@@ -41,9 +41,9 @@ fun LoadRedirectScreen(eventId: String?, accountViewModel: AccountViewModel, nav
|
|||||||
} else if (event is PrivateDmEvent) {
|
} else if (event is PrivateDmEvent) {
|
||||||
navController.backQueue.removeLast()
|
navController.backQueue.removeLast()
|
||||||
navController.navigate("Room/${note.author?.pubkeyHex}")
|
navController.navigate("Room/${note.author?.pubkeyHex}")
|
||||||
} else if (channel != null) {
|
} else if (channelHex != null) {
|
||||||
navController.backQueue.removeLast()
|
navController.backQueue.removeLast()
|
||||||
navController.navigate("Channel/${channel.idHex}")
|
navController.navigate("Channel/$channelHex")
|
||||||
} else {
|
} else {
|
||||||
navController.backQueue.removeLast()
|
navController.backQueue.removeLast()
|
||||||
navController.navigate("Note/${note.idHex}")
|
navController.navigate("Note/${note.idHex}")
|
||||||
|
|||||||
Reference in New Issue
Block a user