|
|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
package com.utun.chat.ui.components |
|
|
|
|
|
|
|
|
|
import androidx.compose.foundation.Canvas |
|
|
|
|
import androidx.compose.foundation.clickable |
|
|
|
|
import androidx.compose.foundation.layout.* |
|
|
|
|
import androidx.compose.foundation.rememberScrollState |
|
|
|
|
@ -8,7 +9,15 @@ import androidx.compose.material3.*
|
|
|
|
|
import androidx.compose.runtime.* |
|
|
|
|
import androidx.compose.runtime.saveable.rememberSaveable |
|
|
|
|
import androidx.compose.ui.Modifier |
|
|
|
|
import androidx.compose.ui.geometry.Offset |
|
|
|
|
import androidx.compose.ui.graphics.Color |
|
|
|
|
import androidx.compose.ui.graphics.Path |
|
|
|
|
import androidx.compose.ui.graphics.StrokeJoin |
|
|
|
|
import androidx.compose.ui.graphics.drawscope.Stroke |
|
|
|
|
import androidx.compose.ui.graphics.drawscope.scale |
|
|
|
|
import androidx.compose.ui.res.stringResource |
|
|
|
|
import androidx.compose.ui.semantics.contentDescription |
|
|
|
|
import androidx.compose.ui.semantics.semantics |
|
|
|
|
import androidx.compose.ui.unit.dp |
|
|
|
|
import com.utun.chat.R |
|
|
|
|
import com.utun.chat.data.* |
|
|
|
|
@ -45,10 +54,14 @@ fun CreatePollDialog(onDismiss: () -> Unit, onCreate: (String, List<String>, Boo
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Composable |
|
|
|
|
fun PollMessageBubble(message: Message, onVote: (Message, List<Int>, String) -> Unit, authorName: (Long) -> String) { |
|
|
|
|
fun PollMessageBubble(message: Message, onVote: (Message, List<Int>, String) -> Unit, authorName: (Long) -> String, |
|
|
|
|
footer: @Composable () -> Unit = {}) { |
|
|
|
|
var showVote by rememberSaveable(message.messageId) { mutableStateOf(false) } |
|
|
|
|
val poll = message.feedback?.poll |
|
|
|
|
Card(onClick = { if (poll != null) showVote = true }, modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), |
|
|
|
|
Row(Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 2.dp), |
|
|
|
|
horizontalArrangement = if (message.isOutgoing) Arrangement.End else Arrangement.Start) { |
|
|
|
|
MessageWithFooter(footer) { |
|
|
|
|
Card(onClick = { if (poll != null) showVote = true }, modifier = Modifier.widthIn(max = 300.dp), |
|
|
|
|
colors = CardDefaults.cardColors(containerColor = if (message.isOutgoing) MaterialTheme.colorScheme.primaryContainer |
|
|
|
|
else MaterialTheme.colorScheme.surfaceVariant)) { |
|
|
|
|
Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { |
|
|
|
|
@ -70,6 +83,8 @@ fun PollMessageBubble(message: Message, onVote: (Message, List<Int>, String) ->
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (showVote && poll != null) PollVoteDialog(message, poll, { showVote = false }, onVote, authorName) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -123,12 +138,20 @@ private fun PollVoteDialog(message: Message, poll: PollState, onDismiss: () -> U
|
|
|
|
|
@Composable |
|
|
|
|
fun MessageReactions(message: Message, catalog: List<ReactionState>, onReact: (Message, Int?) -> Unit) { |
|
|
|
|
var picker by rememberSaveable(message.messageId) { mutableStateOf(false) } |
|
|
|
|
FlowRow(Modifier.padding(horizontal = 8.dp), horizontalArrangement = Arrangement.spacedBy(4.dp)) { |
|
|
|
|
FlowRow(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(4.dp)) { |
|
|
|
|
IconButton(onClick = { |
|
|
|
|
LogManager.addLog("DEBUG", "POLL", "reaction picker opened message=${message.messageId} type=${message.contentType}") |
|
|
|
|
picker = true |
|
|
|
|
}) { |
|
|
|
|
ReactionHeart(stringResource(R.string.poll_react)) |
|
|
|
|
} |
|
|
|
|
for (reaction in message.feedback?.reactions.orEmpty()) { |
|
|
|
|
FilterChip(selected = reaction.mine, onClick = { onReact(message, reaction.id) }, |
|
|
|
|
FilterChip(selected = reaction.mine, onClick = { |
|
|
|
|
LogManager.addLog("DEBUG", "POLL", "reaction toggled message=${message.messageId} emoji=${reaction.id} mine=${reaction.mine}") |
|
|
|
|
onReact(message, reaction.id) |
|
|
|
|
}, |
|
|
|
|
label = { Text("${reaction.emoji} ${reaction.count}") }) |
|
|
|
|
} |
|
|
|
|
TextButton(onClick = { picker = true }) { Text(stringResource(R.string.poll_react)) } |
|
|
|
|
} |
|
|
|
|
if (picker) AlertDialog(onDismissRequest = { picker = false }, title = { Text(stringResource(R.string.poll_react)) }, |
|
|
|
|
text = { FlowRow(horizontalArrangement = Arrangement.spacedBy(4.dp)) { |
|
|
|
|
@ -139,3 +162,25 @@ fun MessageReactions(message: Message, catalog: List<ReactionState>, onReact: (M
|
|
|
|
|
} }, |
|
|
|
|
dismissButton = { TextButton(onClick = { picker = false }) { Text(stringResource(R.string.close)) } }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Два контура без заливки: более широкий тёмный даёт тень ровно в один физический пиксель. */ |
|
|
|
|
@Composable |
|
|
|
|
private fun ReactionHeart(description: String) { |
|
|
|
|
val path = remember { |
|
|
|
|
Path().apply { |
|
|
|
|
moveTo(12f, 21f) |
|
|
|
|
cubicTo(10.5f, 19.5f, 3f, 14.2f, 3f, 8.5f) |
|
|
|
|
cubicTo(3f, 3f, 9f, 2f, 12f, 6f) |
|
|
|
|
cubicTo(15f, 2f, 21f, 3f, 21f, 8.5f) |
|
|
|
|
cubicTo(21f, 14.2f, 13.5f, 19.5f, 12f, 21f) |
|
|
|
|
close() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
Canvas(Modifier.size(24.dp).semantics { contentDescription = description }) { |
|
|
|
|
val factor = size.width / 24f |
|
|
|
|
scale(factor, factor, pivot = Offset.Zero) { |
|
|
|
|
drawPath(path, Color.Black.copy(alpha = 0.6f), style = Stroke(width = 1.8f + 2f / factor, join = StrokeJoin.Round)) |
|
|
|
|
drawPath(path, Color.White, style = Stroke(width = 1.8f, join = StrokeJoin.Round)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|