3 changed files with 82 additions and 5 deletions
@ -0,0 +1,70 @@
|
||||
package com.utun.chat.ui.components |
||||
|
||||
import androidx.compose.foundation.background |
||||
import androidx.compose.foundation.clickable |
||||
import androidx.compose.foundation.gestures.awaitEachGesture |
||||
import androidx.compose.foundation.gestures.awaitFirstDown |
||||
import androidx.compose.foundation.layout.* |
||||
import androidx.compose.material3.MaterialTheme |
||||
import androidx.compose.material3.Text |
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.getValue |
||||
import androidx.compose.runtime.rememberUpdatedState |
||||
import androidx.compose.ui.Modifier |
||||
import androidx.compose.ui.input.pointer.PointerEventPass |
||||
import androidx.compose.ui.input.pointer.pointerInput |
||||
import androidx.compose.ui.res.stringResource |
||||
import androidx.compose.ui.semantics.onLongClick |
||||
import androidx.compose.ui.semantics.semantics |
||||
import androidx.compose.ui.text.style.TextOverflow |
||||
import androidx.compose.ui.unit.dp |
||||
import com.utun.chat.R |
||||
import com.utun.chat.data.Message |
||||
|
||||
/** Initial-pass позволяет открыть меню поверх ссылок, плеера и кнопок голосования. */ |
||||
@Composable |
||||
internal fun Modifier.messageLongPress(label: String, onLongPress: () -> Unit): Modifier { |
||||
val action by rememberUpdatedState(onLongPress) |
||||
return semantics { onLongClick(label) { action(); true } }.pointerInput(Unit) { |
||||
awaitEachGesture { |
||||
val down = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial) |
||||
val releasedOrMoved = withTimeoutOrNull(viewConfiguration.longPressTimeoutMillis) { |
||||
do { |
||||
val event = awaitPointerEvent(PointerEventPass.Initial) |
||||
val change = event.changes.firstOrNull { it.id == down.id } ?: return@withTimeoutOrNull true |
||||
if (!change.pressed || (change.position - down.position).getDistance() > viewConfiguration.touchSlop) |
||||
return@withTimeoutOrNull true |
||||
} while (true) |
||||
} |
||||
if (releasedOrMoved == null) { |
||||
action() |
||||
do { |
||||
val event = awaitPointerEvent(PointerEventPass.Initial) |
||||
event.changes.forEach { it.consume() } |
||||
} while (event.changes.any { it.pressed }) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Composable |
||||
fun ReplyQuote(original: Message?, modifier: Modifier = Modifier, onClick: (() -> Unit)? = null) { |
||||
val preview = when { |
||||
original == null -> stringResource(R.string.reply_original_missing) |
||||
original.contentType == "audio/opus" -> stringResource(R.string.reply_voice) |
||||
original.contentType.startsWith("image/") -> stringResource(R.string.photo) |
||||
original.contentType.startsWith("video/") -> stringResource(R.string.video_message) |
||||
original.contentType == "poll" -> original.feedback?.poll?.question ?: stringResource(R.string.reply_poll) |
||||
else -> original.text.replace(Regex("\\s+"), " ").take(160) |
||||
} |
||||
Row(modifier.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier) |
||||
.height(IntrinsicSize.Min).padding(vertical = 4.dp)) { |
||||
Box(Modifier.width(3.dp).fillMaxHeight().background(MaterialTheme.colorScheme.primary)) |
||||
Column(Modifier.padding(horizontal = 8.dp).weight(1f)) { |
||||
Text(original?.let { if (it.isOutgoing) stringResource(R.string.you) else it.author } ?: stringResource(R.string.reply), |
||||
style = MaterialTheme.typography.labelMedium, color = MaterialTheme.colorScheme.primary, |
||||
maxLines = 1, overflow = TextOverflow.Ellipsis) |
||||
Text(preview, style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis) |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue