Zaps, Likes and Replies in Public Chats and Private Messages
This commit is contained in:
@@ -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,7 +144,9 @@ fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote
|
|||||||
|
|
||||||
Column() {
|
Column() {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(1f).padding(
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(1f)
|
||||||
|
.padding(
|
||||||
start = 12.dp,
|
start = 12.dp,
|
||||||
end = 12.dp,
|
end = 12.dp,
|
||||||
top = 5.dp,
|
top = 5.dp,
|
||||||
@@ -129,9 +154,12 @@ fun ChatroomMessageCompose(baseNote: Note, routeForLastRead: String?, innerQuote
|
|||||||
),
|
),
|
||||||
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) {
|
||||||
|
Modifier.widthIn(bubbleSize.width.toDp(), availableBubbleSize.width.toDp())
|
||||||
|
}
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
|
Row() {
|
||||||
Text(
|
Text(
|
||||||
timeAgoLong(note.event?.createdAt),
|
timeAgoShort(note.event?.createdAt),
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
fontSize = 12.sp
|
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)),
|
||||||
|
|||||||
@@ -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,23 +104,48 @@ 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
|
||||||
) {
|
) {
|
||||||
|
ReplyReaction(baseNote, accountViewModel, Modifier.weight(1f)) {
|
||||||
|
wantsToReplyTo = baseNote
|
||||||
|
}
|
||||||
|
|
||||||
|
BoostReaction(baseNote, accountViewModel, Modifier.weight(1f)) {
|
||||||
|
wantsToQuote = baseNote
|
||||||
|
}
|
||||||
|
|
||||||
|
LikeReaction(baseNote, accountViewModel, Modifier.weight(1f))
|
||||||
|
|
||||||
|
ZapReaction(baseNote, accountViewModel, Modifier.weight(1f))
|
||||||
|
|
||||||
|
ViewCountReaction(baseNote, Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@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(
|
IconButton(
|
||||||
modifier = Modifier.then(Modifier.size(20.dp)),
|
modifier = Modifier.then(Modifier.size(20.dp)),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (account.isWriteable())
|
if (accountViewModel.isWriteable())
|
||||||
wantsToReplyTo = baseNote
|
onPress()
|
||||||
else
|
else
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
@@ -153,17 +164,35 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showCounter)
|
||||||
Text(
|
Text(
|
||||||
" ${showCount(replies.size)}",
|
" ${showCount(replies.size)}",
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = grayTint,
|
color = grayTint,
|
||||||
modifier = Modifier.weight(1f)
|
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(
|
IconButton(
|
||||||
modifier = Modifier.then(Modifier.size(20.dp)),
|
modifier = Modifier.then(Modifier.size(20.dp)),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (account.isWriteable())
|
if (accountViewModel.isWriteable())
|
||||||
wantsToBoost = true
|
wantsToBoost = true
|
||||||
else
|
else
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -184,12 +213,12 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
},
|
},
|
||||||
onQuote = {
|
onQuote = {
|
||||||
wantsToBoost = false
|
wantsToBoost = false
|
||||||
wantsToQuote = baseNote
|
onQuotePress()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (boostedNote?.isBoostedBy(account.userProfile()) == true) {
|
if (boostedNote?.isBoostedBy(accountViewModel.userProfile()) == true) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_retweeted),
|
painter = painterResource(R.drawable.ic_retweeted),
|
||||||
null,
|
null,
|
||||||
@@ -210,13 +239,27 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
" ${showCount(boostedNote?.boosts?.size)}",
|
" ${showCount(boostedNote?.boosts?.size)}",
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
modifier = Modifier.weight(1f)
|
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(
|
IconButton(
|
||||||
modifier = Modifier.then(Modifier.size(20.dp)),
|
modifier = Modifier.then(Modifier.size(20.dp)),
|
||||||
onClick = {
|
onClick = {
|
||||||
if (account.isWriteable())
|
if (accountViewModel.isWriteable())
|
||||||
accountViewModel.reactTo(baseNote)
|
accountViewModel.reactTo(baseNote)
|
||||||
else
|
else
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -228,7 +271,7 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (reactedNote?.isReactedBy(account.userProfile()) == true) {
|
if (reactedNote?.isReactedBy(accountViewModel.userProfile()) == true) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_liked),
|
painter = painterResource(R.drawable.ic_liked),
|
||||||
null,
|
null,
|
||||||
@@ -249,10 +292,31 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
" ${showCount(reactedNote?.reactions?.size)}",
|
" ${showCount(reactedNote?.reactions?.size)}",
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
modifier = Modifier.weight(1f)
|
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(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.then(Modifier.size(20.dp))
|
.then(Modifier.size(20.dp))
|
||||||
@@ -271,7 +335,7 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
} else if (!account.isWriteable()) {
|
} else if (!accountViewModel.isWriteable()) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Toast
|
Toast
|
||||||
.makeText(
|
.makeText(
|
||||||
@@ -336,8 +400,14 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
showAmount(zappedNote?.zappedAmount()),
|
showAmount(zappedNote?.zappedAmount()),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
modifier = Modifier.weight(1f)
|
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(
|
IconButton(
|
||||||
modifier = Modifier.then(Modifier.size(20.dp)),
|
modifier = Modifier.then(Modifier.size(20.dp)),
|
||||||
@@ -351,7 +421,7 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(modifier = Modifier.weight(1f)) {
|
Row(modifier = textModifier) {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
.data("https://counter.amethyst.social/${baseNote.idHex}.svg?label=+&color=00000000")
|
.data("https://counter.amethyst.social/${baseNote.idHex}.svg?label=+&color=00000000")
|
||||||
@@ -365,9 +435,6 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@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(),
|
||||||
|
|||||||
Reference in New Issue
Block a user