mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-29 16:30:49 +00:00
Zaps, Likes and Replies in Public Chats and Private Messages
This commit is contained in:
parent
236177c6ce
commit
bc50f08ca2
@ -289,14 +289,50 @@ class Account(
|
|||||||
LocalCache.consume(signedEvent, null)
|
LocalCache.consume(signedEvent, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createPrivateMessageWithReply(
|
||||||
|
recipientPubKey: ByteArray,
|
||||||
|
msg: String,
|
||||||
|
replyTos: List<String>? = null, mentions: List<String>? = null,
|
||||||
|
privateKey: ByteArray,
|
||||||
|
createdAt: Long = Date().time / 1000,
|
||||||
|
publishedRecipientPubKey: ByteArray? = null,
|
||||||
|
advertiseNip18: Boolean = true
|
||||||
|
): PrivateDmEvent {
|
||||||
|
val content = Utils.encrypt(
|
||||||
|
if (advertiseNip18) {
|
||||||
|
PrivateDmEvent.nip18Advertisement
|
||||||
|
} else { "" } + msg,
|
||||||
|
privateKey,
|
||||||
|
recipientPubKey)
|
||||||
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
|
val tags = mutableListOf<List<String>>()
|
||||||
|
publishedRecipientPubKey?.let {
|
||||||
|
tags.add(listOf("p", publishedRecipientPubKey.toHex()))
|
||||||
|
}
|
||||||
|
replyTos?.forEach {
|
||||||
|
tags.add(listOf("e", it))
|
||||||
|
}
|
||||||
|
mentions?.forEach {
|
||||||
|
tags.add(listOf("p", it))
|
||||||
|
}
|
||||||
|
val id = Event.generateId(pubKey, createdAt, PrivateDmEvent.kind, tags, content)
|
||||||
|
val sig = Utils.sign(id, privateKey)
|
||||||
|
return PrivateDmEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
}
|
||||||
|
|
||||||
fun sendPrivateMeesage(message: String, toUser: String, replyingTo: Note? = null) {
|
fun sendPrivateMeesage(message: String, toUser: String, replyingTo: Note? = null) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
val user = LocalCache.users[toUser] ?: return
|
val user = LocalCache.users[toUser] ?: return
|
||||||
|
|
||||||
val signedEvent = PrivateDmEvent.create(
|
val repliesToHex = listOfNotNull(replyingTo?.idHex).ifEmpty { null }
|
||||||
|
val mentionsHex = emptyList<String>()
|
||||||
|
|
||||||
|
val signedEvent = createPrivateMessageWithReply(
|
||||||
recipientPubKey = user.pubkey(),
|
recipientPubKey = user.pubkey(),
|
||||||
publishedRecipientPubKey = user.pubkey(),
|
publishedRecipientPubKey = user.pubkey(),
|
||||||
msg = message,
|
msg = message,
|
||||||
|
replyTos = repliesToHex,
|
||||||
|
mentions = mentionsHex,
|
||||||
privateKey = loggedIn.privKey!!,
|
privateKey = loggedIn.privKey!!,
|
||||||
advertiseNip18 = false
|
advertiseNip18 = false
|
||||||
)
|
)
|
||||||
|
@ -1,34 +1,48 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.requiredWidth
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Divider
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Bolt
|
||||||
import androidx.compose.material.icons.filled.ChevronRight
|
import androidx.compose.material.icons.filled.ChevronRight
|
||||||
|
import androidx.compose.material.icons.outlined.Bolt
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -38,16 +52,23 @@ import androidx.compose.ui.graphics.ColorFilter
|
|||||||
import androidx.compose.ui.graphics.ColorMatrix
|
import androidx.compose.ui.graphics.ColorMatrix
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.graphics.painter.BitmapPainter
|
import androidx.compose.ui.graphics.painter.BitmapPainter
|
||||||
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import com.google.accompanist.flowlayout.FlowRow
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
import com.vitorpamplona.amethyst.NotificationCache
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.RoboHashCache
|
import com.vitorpamplona.amethyst.RoboHashCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
@ -56,13 +77,15 @@ import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
|||||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
val ChatBubbleShapeMe = RoundedCornerShape(15.dp, 15.dp, 3.dp, 15.dp)
|
val ChatBubbleShapeMe = RoundedCornerShape(15.dp, 15.dp, 3.dp, 15.dp)
|
||||||
val ChatBubbleShapeThem = RoundedCornerShape(3.dp, 15.dp, 15.dp, 15.dp)
|
val ChatBubbleShapeThem = RoundedCornerShape(3.dp, 15.dp, 15.dp, 15.dp)
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote: Boolean = false, accountViewModel: AccountViewModel, navController: NavController) {
|
fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote: Boolean = false, accountViewModel: AccountViewModel, navController: NavController, onWantsToReply: (Note) -> Unit) {
|
||||||
val noteState by baseNote.live().metadata.observeAsState()
|
val noteState by baseNote.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = noteState?.note
|
||||||
|
|
||||||
@ -121,17 +144,22 @@ fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote
|
|||||||
|
|
||||||
Column() {
|
Column() {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(1f).padding(
|
modifier = Modifier
|
||||||
start = 12.dp,
|
.fillMaxWidth(1f)
|
||||||
end = 12.dp,
|
.padding(
|
||||||
top = 5.dp,
|
start = 12.dp,
|
||||||
bottom = 5.dp
|
end = 12.dp,
|
||||||
),
|
top = 5.dp,
|
||||||
|
bottom = 5.dp
|
||||||
|
),
|
||||||
horizontalArrangement = alignment
|
horizontalArrangement = alignment
|
||||||
) {
|
) {
|
||||||
|
var availableBubbleSize by remember { mutableStateOf(IntSize.Zero) }
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = alignment,
|
horizontalArrangement = alignment,
|
||||||
modifier = Modifier.fillMaxWidth(if (innerQuote) 1f else 0.85f)
|
modifier = Modifier.fillMaxWidth(if (innerQuote) 1f else 0.85f).onSizeChanged {
|
||||||
|
availableBubbleSize = it
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
@ -143,8 +171,12 @@ fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote
|
|||||||
onLongClick = { popupExpanded = true }
|
onLongClick = { popupExpanded = true }
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
var bubbleSize by remember { mutableStateOf(IntSize.Zero) }
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 5.dp),
|
modifier = Modifier.padding(start = 10.dp, end = 5.dp, bottom = 5.dp).onSizeChanged {
|
||||||
|
bubbleSize = it
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val authorState by note.author!!.live().metadata.observeAsState()
|
val authorState by note.author!!.live().metadata.observeAsState()
|
||||||
@ -195,7 +227,8 @@ fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote
|
|||||||
null,
|
null,
|
||||||
innerQuote = true,
|
innerQuote = true,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
navController = navController
|
navController = navController,
|
||||||
|
onWantsToReply = onWantsToReply
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -244,20 +277,37 @@ fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote
|
|||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
modifier = Modifier.padding(top = 2.dp)
|
modifier = Modifier.padding(top = 2.dp).then(
|
||||||
) {
|
with(LocalDensity.current) {
|
||||||
Text(
|
Modifier.widthIn(bubbleSize.width.toDp(), availableBubbleSize.width.toDp())
|
||||||
timeAgoLong(note.event?.createdAt),
|
}
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
||||||
fontSize = 12.sp
|
|
||||||
)
|
)
|
||||||
|
) {
|
||||||
|
Row() {
|
||||||
|
Text(
|
||||||
|
timeAgoShort(note.event?.createdAt),
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
|
||||||
RelayBadges(note)
|
RelayBadges(note)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
Row() {
|
||||||
|
LikeReaction(baseNote, accountViewModel)
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
ZapReaction(baseNote, accountViewModel)
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
ReplyReaction(baseNote, accountViewModel, showCounter = false) {
|
||||||
|
onWantsToReply(baseNote)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
||||||
@ -266,8 +316,6 @@ fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RelayBadges(baseNote: Note) {
|
private fun RelayBadges(baseNote: Note) {
|
||||||
val noteRelaysState by baseNote.live().relays.observeAsState()
|
val noteRelaysState by baseNote.live().relays.observeAsState()
|
||||||
@ -283,7 +331,10 @@ private fun RelayBadges(baseNote: Note) {
|
|||||||
FlowRow(Modifier.padding(start = 10.dp)) {
|
FlowRow(Modifier.padding(start = 10.dp)) {
|
||||||
relaysToDisplay.forEach {
|
relaysToDisplay.forEach {
|
||||||
val url = it.removePrefix("wss://")
|
val url = it.removePrefix("wss://")
|
||||||
Box(Modifier.size(15.dp).padding(1.dp)) {
|
Box(
|
||||||
|
Modifier
|
||||||
|
.size(15.dp)
|
||||||
|
.padding(1.dp)) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = "https://${url}/favicon.ico",
|
model = "https://${url}/favicon.ico",
|
||||||
placeholder = BitmapPainter(RoboHashCache.get(ctx, url)),
|
placeholder = BitmapPainter(RoboHashCache.get(ctx, url)),
|
||||||
@ -295,7 +346,7 @@ private fun RelayBadges(baseNote: Note) {
|
|||||||
.fillMaxSize(1f)
|
.fillMaxSize(1f)
|
||||||
.clip(shape = CircleShape)
|
.clip(shape = CircleShape)
|
||||||
.background(MaterialTheme.colors.background)
|
.background(MaterialTheme.colors.background)
|
||||||
.clickable(onClick = { uri.openUri("https://" + url) } )
|
.clickable(onClick = { uri.openUri("https://" + url) })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
@ -49,6 +50,7 @@ import androidx.compose.ui.graphics.ColorFilter
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import androidx.compose.ui.platform.UriHandler
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
@ -78,6 +80,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@ -87,23 +90,6 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account ?: return
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
val reactionsState by baseNote.live().reactions.observeAsState()
|
|
||||||
val reactedNote = reactionsState?.note
|
|
||||||
|
|
||||||
val boostsState by baseNote.live().boosts.observeAsState()
|
|
||||||
val boostedNote = boostsState?.note
|
|
||||||
|
|
||||||
val zapsState by baseNote.live().zaps.observeAsState()
|
|
||||||
val zappedNote = zapsState?.note
|
|
||||||
|
|
||||||
val repliesState by baseNote.live().replies.observeAsState()
|
|
||||||
val replies = repliesState?.note?.replies ?: emptySet()
|
|
||||||
|
|
||||||
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
|
||||||
val uri = LocalUriHandler.current
|
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
var wantsToReplyTo by remember {
|
var wantsToReplyTo by remember {
|
||||||
mutableStateOf<Note?>(null)
|
mutableStateOf<Note?>(null)
|
||||||
}
|
}
|
||||||
@ -118,256 +104,337 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
if (wantsToQuote != null)
|
if (wantsToQuote != null)
|
||||||
NewPostView({ wantsToQuote = null }, null, wantsToQuote, account)
|
NewPostView({ wantsToQuote = null }, null, wantsToQuote, account)
|
||||||
|
|
||||||
var wantsToZap by remember { mutableStateOf(false) }
|
|
||||||
var wantsToChangeZapAmount by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
var wantsToBoost by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(top = 8.dp).fillMaxWidth(),
|
||||||
.padding(top = 8.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
IconButton(
|
ReplyReaction(baseNote, accountViewModel, Modifier.weight(1f)) {
|
||||||
modifier = Modifier.then(Modifier.size(20.dp)),
|
wantsToReplyTo = baseNote
|
||||||
onClick = {
|
|
||||||
if (account.isWriteable())
|
|
||||||
wantsToReplyTo = baseNote
|
|
||||||
else
|
|
||||||
scope.launch {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
"Login with a Private key to be able to reply",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_comment),
|
|
||||||
null,
|
|
||||||
modifier = Modifier.size(15.dp),
|
|
||||||
tint = grayTint,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
BoostReaction(baseNote, accountViewModel, Modifier.weight(1f)) {
|
||||||
" ${showCount(replies.size)}",
|
wantsToQuote = baseNote
|
||||||
fontSize = 14.sp,
|
|
||||||
color = grayTint,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
IconButton(
|
|
||||||
modifier = Modifier.then(Modifier.size(20.dp)),
|
|
||||||
onClick = {
|
|
||||||
if (account.isWriteable())
|
|
||||||
wantsToBoost = true
|
|
||||||
else
|
|
||||||
scope.launch {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
"Login with a Private key to be able to boost posts",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
if (wantsToBoost) {
|
|
||||||
BoostTypeChoicePopup(
|
|
||||||
baseNote,
|
|
||||||
accountViewModel,
|
|
||||||
onDismiss = {
|
|
||||||
wantsToBoost = false
|
|
||||||
},
|
|
||||||
onQuote = {
|
|
||||||
wantsToBoost = false
|
|
||||||
wantsToQuote = baseNote
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (boostedNote?.isBoostedBy(account.userProfile()) == true) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_retweeted),
|
|
||||||
null,
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
tint = Color.Unspecified
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_retweet),
|
|
||||||
null,
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
tint = grayTint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
LikeReaction(baseNote, accountViewModel, Modifier.weight(1f))
|
||||||
" ${showCount(boostedNote?.boosts?.size)}",
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
IconButton(
|
ZapReaction(baseNote, accountViewModel, Modifier.weight(1f))
|
||||||
modifier = Modifier.then(Modifier.size(20.dp)),
|
|
||||||
onClick = {
|
|
||||||
if (account.isWriteable())
|
|
||||||
accountViewModel.reactTo(baseNote)
|
|
||||||
else
|
|
||||||
scope.launch {
|
|
||||||
Toast.makeText(
|
|
||||||
context,
|
|
||||||
"Login with a Private key to like Posts",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
if (reactedNote?.isReactedBy(account.userProfile()) == true) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_liked),
|
|
||||||
null,
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
tint = Color.Unspecified
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_like),
|
|
||||||
null,
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
tint = grayTint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
ViewCountReaction(baseNote, Modifier.weight(1f))
|
||||||
" ${showCount(reactedNote?.reactions?.size)}",
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.then(Modifier.size(20.dp))
|
|
||||||
.combinedClickable(
|
|
||||||
role = Role.Button,
|
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
|
||||||
indication = rememberRipple(bounded = false, radius = 24.dp),
|
|
||||||
onClick = {
|
|
||||||
if (account.zapAmountChoices.isEmpty()) {
|
|
||||||
scope.launch {
|
|
||||||
Toast
|
|
||||||
.makeText(
|
|
||||||
context,
|
|
||||||
"No Zap Amount Setup. Long Press to change",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
} else if (!account.isWriteable()) {
|
|
||||||
scope.launch {
|
|
||||||
Toast
|
|
||||||
.makeText(
|
|
||||||
context,
|
|
||||||
"Login with a Private key to be able to send Zaps",
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
} else if (account.zapAmountChoices.size == 1) {
|
|
||||||
accountViewModel.zap(baseNote, account.zapAmountChoices.first() * 1000, "", context) {
|
|
||||||
scope.launch {
|
|
||||||
Toast
|
|
||||||
.makeText(context, it, Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (account.zapAmountChoices.size > 1) {
|
|
||||||
wantsToZap = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLongClick = {
|
|
||||||
wantsToChangeZapAmount = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (wantsToZap) {
|
|
||||||
ZapAmountChoicePopup(
|
|
||||||
baseNote,
|
|
||||||
accountViewModel,
|
|
||||||
onDismiss = {
|
|
||||||
wantsToZap = false
|
|
||||||
},
|
|
||||||
onChangeAmount = {
|
|
||||||
wantsToZap = false
|
|
||||||
wantsToChangeZapAmount = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (wantsToChangeZapAmount) {
|
|
||||||
UpdateZapAmountDialog({ wantsToChangeZapAmount = false }, account = account)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zappedNote?.isZappedBy(account.userProfile()) == true) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Bolt,
|
|
||||||
contentDescription = "Zaps",
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
tint = BitcoinOrange
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.Bolt,
|
|
||||||
contentDescription = "Zaps",
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
tint = grayTint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
|
||||||
showAmount(zappedNote?.zappedAmount()),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
IconButton(
|
|
||||||
modifier = Modifier.then(Modifier.size(20.dp)),
|
|
||||||
onClick = { uri.openUri("https://counter.amethyst.social/${baseNote.idHex}/") }
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.BarChart,
|
|
||||||
null,
|
|
||||||
modifier = Modifier.size(19.dp),
|
|
||||||
tint = grayTint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(modifier = Modifier.weight(1f)) {
|
|
||||||
AsyncImage(
|
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
|
||||||
.data("https://counter.amethyst.social/${baseNote.idHex}.svg?label=+&color=00000000")
|
|
||||||
.crossfade(true)
|
|
||||||
.diskCachePolicy(CachePolicy.DISABLED)
|
|
||||||
.memoryCachePolicy(CachePolicy.ENABLED)
|
|
||||||
.build(),
|
|
||||||
contentDescription = "View count",
|
|
||||||
modifier = Modifier.height(24.dp),
|
|
||||||
colorFilter = ColorFilter.tint(grayTint)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReplyReaction(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
textModifier: Modifier = Modifier,
|
||||||
|
showCounter: Boolean = true,
|
||||||
|
onPress: () -> Unit,
|
||||||
|
) {
|
||||||
|
val repliesState by baseNote.live().replies.observeAsState()
|
||||||
|
val replies = repliesState?.note?.replies ?: emptySet()
|
||||||
|
|
||||||
|
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.then(Modifier.size(20.dp)),
|
||||||
|
onClick = {
|
||||||
|
if (accountViewModel.isWriteable())
|
||||||
|
onPress()
|
||||||
|
else
|
||||||
|
scope.launch {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Login with a Private key to be able to reply",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_comment),
|
||||||
|
null,
|
||||||
|
modifier = Modifier.size(15.dp),
|
||||||
|
tint = grayTint,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showCounter)
|
||||||
|
Text(
|
||||||
|
" ${showCount(replies.size)}",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = grayTint,
|
||||||
|
modifier = textModifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BoostReaction(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
textModifier: Modifier = Modifier,
|
||||||
|
onQuotePress: () -> Unit,
|
||||||
|
) {
|
||||||
|
val boostsState by baseNote.live().boosts.observeAsState()
|
||||||
|
val boostedNote = boostsState?.note
|
||||||
|
|
||||||
|
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
var wantsToBoost by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.then(Modifier.size(20.dp)),
|
||||||
|
onClick = {
|
||||||
|
if (accountViewModel.isWriteable())
|
||||||
|
wantsToBoost = true
|
||||||
|
else
|
||||||
|
scope.launch {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Login with a Private key to be able to boost posts",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (wantsToBoost) {
|
||||||
|
BoostTypeChoicePopup(
|
||||||
|
baseNote,
|
||||||
|
accountViewModel,
|
||||||
|
onDismiss = {
|
||||||
|
wantsToBoost = false
|
||||||
|
},
|
||||||
|
onQuote = {
|
||||||
|
wantsToBoost = false
|
||||||
|
onQuotePress()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boostedNote?.isBoostedBy(accountViewModel.userProfile()) == true) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_retweeted),
|
||||||
|
null,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_retweet),
|
||||||
|
null,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = grayTint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
" ${showCount(boostedNote?.boosts?.size)}",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
|
modifier = textModifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LikeReaction(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
textModifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val reactionsState by baseNote.live().reactions.observeAsState()
|
||||||
|
val reactedNote = reactionsState?.note
|
||||||
|
|
||||||
|
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.then(Modifier.size(20.dp)),
|
||||||
|
onClick = {
|
||||||
|
if (accountViewModel.isWriteable())
|
||||||
|
accountViewModel.reactTo(baseNote)
|
||||||
|
else
|
||||||
|
scope.launch {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
"Login with a Private key to like Posts",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (reactedNote?.isReactedBy(accountViewModel.userProfile()) == true) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_liked),
|
||||||
|
null,
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_like),
|
||||||
|
null,
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
tint = grayTint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
" ${showCount(reactedNote?.reactions?.size)}",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
|
modifier = textModifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
fun ZapReaction(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
textModifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
|
val zapsState by baseNote.live().zaps.observeAsState()
|
||||||
|
val zappedNote = zapsState?.note
|
||||||
|
|
||||||
|
var wantsToZap by remember { mutableStateOf(false) }
|
||||||
|
var wantsToChangeZapAmount by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.then(Modifier.size(20.dp))
|
||||||
|
.combinedClickable(
|
||||||
|
role = Role.Button,
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = rememberRipple(bounded = false, radius = 24.dp),
|
||||||
|
onClick = {
|
||||||
|
if (account.zapAmountChoices.isEmpty()) {
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
"No Zap Amount Setup. Long Press to change",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else if (!accountViewModel.isWriteable()) {
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
"Login with a Private key to be able to send Zaps",
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else if (account.zapAmountChoices.size == 1) {
|
||||||
|
accountViewModel.zap(baseNote, account.zapAmountChoices.first() * 1000, "", context) {
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(context, it, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (account.zapAmountChoices.size > 1) {
|
||||||
|
wantsToZap = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
wantsToChangeZapAmount = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (wantsToZap) {
|
||||||
|
ZapAmountChoicePopup(
|
||||||
|
baseNote,
|
||||||
|
accountViewModel,
|
||||||
|
onDismiss = {
|
||||||
|
wantsToZap = false
|
||||||
|
},
|
||||||
|
onChangeAmount = {
|
||||||
|
wantsToZap = false
|
||||||
|
wantsToChangeZapAmount = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (wantsToChangeZapAmount) {
|
||||||
|
UpdateZapAmountDialog({ wantsToChangeZapAmount = false }, account = account)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (zappedNote?.isZappedBy(account.userProfile()) == true) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Bolt,
|
||||||
|
contentDescription = "Zaps",
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = BitcoinOrange
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Bolt,
|
||||||
|
contentDescription = "Zaps",
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = grayTint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
showAmount(zappedNote?.zappedAmount()),
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
|
modifier = textModifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ViewCountReaction(baseNote: Note, textModifier: Modifier = Modifier) {
|
||||||
|
val uri = LocalUriHandler.current
|
||||||
|
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.then(Modifier.size(20.dp)),
|
||||||
|
onClick = { uri.openUri("https://counter.amethyst.social/${baseNote.idHex}/") }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.BarChart,
|
||||||
|
null,
|
||||||
|
modifier = Modifier.size(19.dp),
|
||||||
|
tint = grayTint
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(modifier = textModifier) {
|
||||||
|
AsyncImage(
|
||||||
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data("https://counter.amethyst.social/${baseNote.idHex}.svg?label=+&color=00000000")
|
||||||
|
.crossfade(true)
|
||||||
|
.diskCachePolicy(CachePolicy.DISABLED)
|
||||||
|
.memoryCachePolicy(CachePolicy.ENABLED)
|
||||||
|
.build(),
|
||||||
|
contentDescription = "View count",
|
||||||
|
modifier = Modifier.height(24.dp),
|
||||||
|
colorFilter = ColorFilter.tint(grayTint)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@ -415,7 +482,7 @@ private fun BoostTypeChoicePopup(baseNote: Note, accountViewModel: AccountViewMo
|
|||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ZapAmountChoicePopup(baseNote: Note, accountViewModel: AccountViewModel, onDismiss: () -> Unit, onChangeAmount: () -> Unit) {
|
fun ZapAmountChoicePopup(baseNote: Note, accountViewModel: AccountViewModel, onDismiss: () -> Unit, onChangeAmount: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
@ -22,6 +22,22 @@ fun timeAgo(mills: Long?): String {
|
|||||||
.replace(" days ago", "d")
|
.replace(" days ago", "d")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun timeAgoShort(mills: Long?): String {
|
||||||
|
if (mills == null) return " "
|
||||||
|
|
||||||
|
var humanReadable = DateUtils.getRelativeTimeSpanString(
|
||||||
|
mills * 1000,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
DateUtils.MINUTE_IN_MILLIS,
|
||||||
|
DateUtils.FORMAT_ABBREV_ALL
|
||||||
|
).toString()
|
||||||
|
if (humanReadable.startsWith("In") || humanReadable.startsWith("0")) {
|
||||||
|
humanReadable = "now";
|
||||||
|
}
|
||||||
|
|
||||||
|
return humanReadable
|
||||||
|
}
|
||||||
|
|
||||||
fun timeAgoLong(mills: Long?): String {
|
fun timeAgoLong(mills: Long?): String {
|
||||||
if (mills == null) return " "
|
if (mills == null) return " "
|
||||||
|
|
||||||
|
@ -18,11 +18,12 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose
|
import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController, routeForLastRead: String?) {
|
fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navController: NavController, routeForLastRead: String?, onWantsToReply: (Note) -> Unit ) {
|
||||||
val feedState by viewModel.feedContent.collectAsState()
|
val feedState by viewModel.feedContent.collectAsState()
|
||||||
|
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
@ -65,7 +66,7 @@ fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewMode
|
|||||||
) {
|
) {
|
||||||
var previousDate: String = ""
|
var previousDate: String = ""
|
||||||
itemsIndexed(state.feed.value, key = { index, item -> item.idHex }) { index, item ->
|
itemsIndexed(state.feed.value, key = { index, item -> item.idHex }) { index, item ->
|
||||||
ChatroomMessageCompose(item, routeForLastRead, accountViewModel = accountViewModel, navController = navController)
|
ChatroomMessageCompose(item, routeForLastRead, accountViewModel = accountViewModel, navController = navController, onWantsToReply = onWantsToReply)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,14 @@ class AccountViewModel(private val account: Account): ViewModel() {
|
|||||||
val accountLiveData: LiveData<AccountState> = account.live.map { it }
|
val accountLiveData: LiveData<AccountState> = account.live.map { it }
|
||||||
val accountLanguagesLiveData: LiveData<AccountState> = account.liveLanguages.map { it }
|
val accountLanguagesLiveData: LiveData<AccountState> = account.liveLanguages.map { it }
|
||||||
|
|
||||||
|
fun isWriteable(): Boolean {
|
||||||
|
return account.isWriteable()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun userProfile(): User {
|
||||||
|
return account.userProfile()
|
||||||
|
}
|
||||||
|
|
||||||
fun reactTo(note: Note) {
|
fun reactTo(note: Note) {
|
||||||
account.reactTo(note)
|
account.reactTo(note)
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,13 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@ -18,12 +21,14 @@ import androidx.compose.material.Divider
|
|||||||
import androidx.compose.material.DropdownMenu
|
import androidx.compose.material.DropdownMenu
|
||||||
import androidx.compose.material.DropdownMenuItem
|
import androidx.compose.material.DropdownMenuItem
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.LocalTextStyle
|
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.material.TextField
|
import androidx.compose.material.TextField
|
||||||
import androidx.compose.material.TextFieldDefaults
|
import androidx.compose.material.TextFieldDefaults
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Cancel
|
||||||
import androidx.compose.material.icons.filled.Download
|
import androidx.compose.material.icons.filled.Download
|
||||||
import androidx.compose.material.icons.filled.EditNote
|
import androidx.compose.material.icons.filled.EditNote
|
||||||
import androidx.compose.material.icons.filled.Share
|
import androidx.compose.material.icons.filled.Share
|
||||||
@ -74,6 +79,7 @@ import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
|||||||
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||||
import com.vitorpamplona.amethyst.ui.dal.ChannelFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.ChannelFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import nostr.postr.toNpub
|
import nostr.postr.toNpub
|
||||||
|
|
||||||
@ -84,6 +90,7 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
|
|||||||
|
|
||||||
if (account != null && channelId != null) {
|
if (account != null && channelId != null) {
|
||||||
val newPost = remember { mutableStateOf(TextFieldValue("")) }
|
val newPost = remember { mutableStateOf(TextFieldValue("")) }
|
||||||
|
val replyTo = remember { mutableStateOf<Note?>(null) }
|
||||||
|
|
||||||
ChannelFeedFilter.loadMessagesBetween(account, channelId)
|
ChannelFeedFilter.loadMessagesBetween(account, channelId)
|
||||||
NostrChannelDataSource.loadMessagesBetween(channelId)
|
NostrChannelDataSource.loadMessagesBetween(channelId)
|
||||||
@ -130,13 +137,47 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
|
|||||||
.padding(vertical = 0.dp)
|
.padding(vertical = 0.dp)
|
||||||
.weight(1f, true)
|
.weight(1f, true)
|
||||||
) {
|
) {
|
||||||
ChatroomFeedView(feedViewModel, accountViewModel, navController, "Channel/${channelId}")
|
ChatroomFeedView(feedViewModel, accountViewModel, navController, "Channel/${channelId}") {
|
||||||
|
replyTo.value = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
||||||
|
val replyingNote = replyTo.value
|
||||||
|
if (replyingNote != null) {
|
||||||
|
Row(Modifier.padding(horizontal = 10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Column(Modifier.weight(1f)) {
|
||||||
|
ChatroomMessageCompose(
|
||||||
|
baseNote = replyingNote,
|
||||||
|
null,
|
||||||
|
innerQuote = true,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
navController = navController,
|
||||||
|
onWantsToReply = {
|
||||||
|
replyTo.value = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(Modifier.padding(end = 10.dp)) {
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.size(30.dp),
|
||||||
|
onClick = { replyTo.value = null }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Cancel,
|
||||||
|
null,
|
||||||
|
modifier = Modifier.padding(end = 5.dp).size(30.dp),
|
||||||
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//LAST ROW
|
//LAST ROW
|
||||||
Row(modifier = Modifier
|
Row(modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 10.dp).fillMaxWidth(),
|
||||||
.padding(10.dp)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@ -158,8 +199,9 @@ fun ChannelScreen(channelId: String?, accountViewModel: AccountViewModel, accoun
|
|||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
PostButton(
|
PostButton(
|
||||||
onPost = {
|
onPost = {
|
||||||
account.sendChannelMeesage(newPost.value.text, channel.idHex, null, null)
|
account.sendChannelMeesage(newPost.value.text, channel.idHex, replyTo.value, null)
|
||||||
newPost.value = TextFieldValue("")
|
newPost.value = TextFieldValue("")
|
||||||
|
replyTo.value = null
|
||||||
feedViewModel.refresh() // Don't wait a full second before updating
|
feedViewModel.refresh() // Don't wait a full second before updating
|
||||||
},
|
},
|
||||||
newPost.value.text.isNotBlank(),
|
newPost.value.text.isNotBlank(),
|
||||||
@ -220,7 +262,9 @@ fun ChannelHeader(baseChannel: Channel, account: Account, accountStateViewModel:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(modifier = Modifier.height(35.dp).padding(bottom = 3.dp)) {
|
Row(modifier = Modifier
|
||||||
|
.height(35.dp)
|
||||||
|
.padding(bottom = 3.dp)) {
|
||||||
NoteCopyButton(channel)
|
NoteCopyButton(channel)
|
||||||
|
|
||||||
if (channel.creator == account.userProfile()) {
|
if (channel.creator == account.userProfile()) {
|
||||||
@ -253,7 +297,9 @@ private fun NoteCopyButton(
|
|||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(horizontal = 3.dp).width(50.dp),
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 3.dp)
|
||||||
|
.width(50.dp),
|
||||||
onClick = { popupExpanded = true },
|
onClick = { popupExpanded = true },
|
||||||
shape = RoundedCornerShape(20.dp),
|
shape = RoundedCornerShape(20.dp),
|
||||||
colors = ButtonDefaults
|
colors = ButtonDefaults
|
||||||
@ -288,7 +334,9 @@ private fun EditButton(account: Account, channel: Channel) {
|
|||||||
NewChannelView({ wantsToPost = false }, account = account, channel)
|
NewChannelView({ wantsToPost = false }, account = account, channel)
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
modifier = Modifier.padding(horizontal = 3.dp).width(50.dp),
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 3.dp)
|
||||||
|
.width(50.dp),
|
||||||
onClick = { wantsToPost = true },
|
onClick = { wantsToPost = true },
|
||||||
shape = RoundedCornerShape(20.dp),
|
shape = RoundedCornerShape(20.dp),
|
||||||
colors = ButtonDefaults
|
colors = ButtonDefaults
|
||||||
|
@ -4,20 +4,26 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.Divider
|
import androidx.compose.material.Divider
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.LocalTextStyle
|
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.material.TextField
|
import androidx.compose.material.TextField
|
||||||
import androidx.compose.material.TextFieldDefaults
|
import androidx.compose.material.TextFieldDefaults
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Cancel
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@ -41,6 +47,7 @@ import androidx.lifecycle.LifecycleEventObserver
|
|||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.vitorpamplona.amethyst.RoboHashCache
|
import com.vitorpamplona.amethyst.RoboHashCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
|
import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
|
||||||
@ -49,6 +56,7 @@ import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
|||||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||||
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||||
import com.vitorpamplona.amethyst.ui.dal.ChatroomFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.ChatroomFeedFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose
|
||||||
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
@ -59,6 +67,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
|
|||||||
|
|
||||||
if (account != null && userId != null) {
|
if (account != null && userId != null) {
|
||||||
val newPost = remember { mutableStateOf(TextFieldValue("")) }
|
val newPost = remember { mutableStateOf(TextFieldValue("")) }
|
||||||
|
val replyTo = remember { mutableStateOf<Note?>(null) }
|
||||||
|
|
||||||
ChatroomFeedFilter.loadMessagesBetween(account, userId)
|
ChatroomFeedFilter.loadMessagesBetween(account, userId)
|
||||||
NostrChatroomDataSource.loadMessagesBetween(account, userId)
|
NostrChatroomDataSource.loadMessagesBetween(account, userId)
|
||||||
@ -104,12 +113,47 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
|
|||||||
.padding(vertical = 0.dp)
|
.padding(vertical = 0.dp)
|
||||||
.weight(1f, true)
|
.weight(1f, true)
|
||||||
) {
|
) {
|
||||||
ChatroomFeedView(feedViewModel, accountViewModel, navController, "Room/${userId}")
|
ChatroomFeedView(feedViewModel, accountViewModel, navController, "Room/${userId}") {
|
||||||
|
replyTo.value = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
||||||
|
val replyingNote = replyTo.value
|
||||||
|
if (replyingNote != null) {
|
||||||
|
Row(Modifier.padding(horizontal = 10.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Column(Modifier.weight(1f)) {
|
||||||
|
ChatroomMessageCompose(
|
||||||
|
baseNote = replyingNote,
|
||||||
|
null,
|
||||||
|
innerQuote = true,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
navController = navController,
|
||||||
|
onWantsToReply = {
|
||||||
|
replyTo.value = it
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(Modifier.padding(end = 10.dp)) {
|
||||||
|
IconButton(
|
||||||
|
modifier = Modifier.size(30.dp),
|
||||||
|
onClick = { replyTo.value = null }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Cancel,
|
||||||
|
null,
|
||||||
|
modifier = Modifier.padding(end = 5.dp).size(30.dp),
|
||||||
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//LAST ROW
|
//LAST ROW
|
||||||
Row(modifier = Modifier
|
Row(modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 10.dp, top = 5.dp)
|
||||||
.padding(10.dp)
|
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
@ -132,8 +176,9 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
|
|||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
PostButton(
|
PostButton(
|
||||||
onPost = {
|
onPost = {
|
||||||
account.sendPrivateMeesage(newPost.value.text, userId)
|
account.sendPrivateMeesage(newPost.value.text, userId, replyTo.value)
|
||||||
newPost.value = TextFieldValue("")
|
newPost.value = TextFieldValue("")
|
||||||
|
replyTo.value = null
|
||||||
feedViewModel.refresh() // Don't wait a full second before updating
|
feedViewModel.refresh() // Don't wait a full second before updating
|
||||||
},
|
},
|
||||||
newPost.value.text.isNotBlank(),
|
newPost.value.text.isNotBlank(),
|
||||||
|
Loading…
Reference in New Issue
Block a user