Профиль и группы: фиксированные табы, fast-scroll с датой и Apple Emoji
This commit is contained in:
@@ -2919,14 +2919,15 @@ fun ChatItem(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
AppleEmojiText(
|
||||
text = chat.name,
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
overflow = android.text.TextUtils.TruncateAt.END,
|
||||
modifier = Modifier.weight(1f),
|
||||
enableLinks = false
|
||||
)
|
||||
|
||||
if (isMuted) {
|
||||
@@ -3722,13 +3723,14 @@ fun DialogItemContent(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
AppleEmojiText(
|
||||
text = displayName,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 16.sp,
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
overflow = android.text.TextUtils.TruncateAt.END,
|
||||
enableLinks = false
|
||||
)
|
||||
if (isGroupDialog) {
|
||||
Spacer(modifier = Modifier.width(5.dp))
|
||||
|
||||
@@ -14,6 +14,7 @@ import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -30,6 +31,10 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.itemsIndexed as gridItemsIndexed
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
@@ -82,7 +87,6 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
@@ -124,6 +128,7 @@ import com.rosetta.messenger.ui.chats.components.ImageViewerScreen
|
||||
import com.rosetta.messenger.ui.chats.components.ViewableImage
|
||||
import com.rosetta.messenger.ui.components.AppleEmojiText
|
||||
import com.rosetta.messenger.ui.components.AvatarImage
|
||||
import com.rosetta.messenger.ui.components.SharedMediaFastScrollOverlay
|
||||
import com.rosetta.messenger.ui.components.VerifiedBadge
|
||||
import com.rosetta.messenger.ui.icons.TelegramIcons
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -133,9 +138,13 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import androidx.core.view.WindowCompat
|
||||
import org.json.JSONArray
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.UUID
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private enum class GroupInfoTab(val title: String) {
|
||||
MEMBERS("Members"),
|
||||
@@ -370,6 +379,8 @@ fun GroupInfoScreen(
|
||||
var showImageViewer by rememberSaveable(dialogPublicKey) { mutableStateOf(false) }
|
||||
var imageViewerInitialIndex by rememberSaveable(dialogPublicKey) { mutableStateOf(0) }
|
||||
var imageViewerSourceBounds by remember(dialogPublicKey) { mutableStateOf<ImageSourceBounds?>(null) }
|
||||
val groupMediaGridState = rememberLazyGridState()
|
||||
var groupMediaFastScrollHintDismissed by rememberSaveable(dialogPublicKey) { mutableStateOf(false) }
|
||||
|
||||
val groupTitle = remember(groupEntity, groupUser.title) {
|
||||
groupEntity?.title?.trim().takeUnless { it.isNullOrBlank() }
|
||||
@@ -1134,55 +1145,95 @@ fun GroupInfoScreen(
|
||||
} else {
|
||||
val mediaColumns = 3
|
||||
val mediaSpacing = 1.dp
|
||||
val mediaScreenWidth = LocalConfiguration.current.screenWidthDp.dp
|
||||
val mediaCellSize =
|
||||
(mediaScreenWidth - mediaSpacing * (mediaColumns - 1)) / mediaColumns
|
||||
val mediaIndexedRows = remember(groupMediaItems) {
|
||||
groupMediaItems.chunked(mediaColumns).mapIndexed { idx, row -> idx to row }
|
||||
val mediaRowsCount = remember(groupMediaItems.size) {
|
||||
ceil(groupMediaItems.size / mediaColumns.toFloat()).toInt().coerceAtLeast(1)
|
||||
}
|
||||
val mediaFastScrollVisible by remember(groupMediaItems.size, groupMediaGridState) {
|
||||
derivedStateOf {
|
||||
val visibleItems = groupMediaGridState.layoutInfo.visibleItemsInfo
|
||||
if (visibleItems.isEmpty()) return@derivedStateOf false
|
||||
val cellHeight = visibleItems.first().size.height
|
||||
val viewportHeight = groupMediaGridState.layoutInfo.viewportEndOffset -
|
||||
groupMediaGridState.layoutInfo.viewportStartOffset
|
||||
mediaRowsCount * cellHeight > viewportHeight
|
||||
}
|
||||
}
|
||||
val mediaFastScrollProgress by remember(groupMediaItems.size, groupMediaGridState) {
|
||||
derivedStateOf {
|
||||
val visibleItems = groupMediaGridState.layoutInfo.visibleItemsInfo
|
||||
if (visibleItems.isEmpty()) return@derivedStateOf 0f
|
||||
val cellHeight = visibleItems.first().size.height
|
||||
if (cellHeight <= 0) return@derivedStateOf 0f
|
||||
val viewportHeight = groupMediaGridState.layoutInfo.viewportEndOffset -
|
||||
groupMediaGridState.layoutInfo.viewportStartOffset
|
||||
val totalHeight = mediaRowsCount * cellHeight
|
||||
val maxScroll = (totalHeight - viewportHeight).coerceAtLeast(1)
|
||||
val firstRow = groupMediaGridState.firstVisibleItemIndex / mediaColumns
|
||||
val scrollY = firstRow * cellHeight + groupMediaGridState.firstVisibleItemScrollOffset
|
||||
(scrollY.toFloat() / maxScroll.toFloat()).coerceIn(0f, 1f)
|
||||
}
|
||||
}
|
||||
val mediaFastScrollMonthLabel by remember(groupMediaItems, groupMediaGridState.firstVisibleItemIndex) {
|
||||
derivedStateOf {
|
||||
if (groupMediaItems.isEmpty()) return@derivedStateOf ""
|
||||
val index = groupMediaGridState.firstVisibleItemIndex.coerceIn(0, groupMediaItems.lastIndex)
|
||||
formatMediaMonthLabel(groupMediaItems[index].timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 20.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(mediaSpacing)
|
||||
) {
|
||||
items(mediaIndexedRows, key = { (idx, _) -> "group_media_row_$idx" }) { (rowIdx, rowMedia) ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(mediaSpacing)
|
||||
) {
|
||||
rowMedia.forEachIndexed { colIdx, mediaItem ->
|
||||
val globalIndex = rowIdx * mediaColumns + colIdx
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(mediaCellSize)
|
||||
.clip(RoundedCornerShape(0.dp))
|
||||
.background(if (isDarkTheme) Color(0xFF141416) else Color(0xFFE6E7EB))
|
||||
) {
|
||||
ImageAttachment(
|
||||
attachment = mediaItem.attachment,
|
||||
chachaKey = mediaItem.chachaKey,
|
||||
privateKey = currentUserPrivateKey,
|
||||
senderPublicKey = mediaItem.senderPublicKey,
|
||||
isOutgoing = mediaItem.senderPublicKey == currentUserPublicKey,
|
||||
isDarkTheme = isDarkTheme,
|
||||
timestamp = Date(mediaItem.timestamp),
|
||||
showTimeOverlay = false,
|
||||
fillMaxSize = true,
|
||||
onImageClick = { _, bounds ->
|
||||
imageViewerInitialIndex = globalIndex
|
||||
imageViewerSourceBounds = bounds
|
||||
showImageViewer = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
repeat(mediaColumns - rowMedia.size) {
|
||||
Spacer(modifier = Modifier.size(mediaCellSize))
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(mediaColumns),
|
||||
state = groupMediaGridState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(bottom = 20.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(mediaSpacing),
|
||||
verticalArrangement = Arrangement.spacedBy(mediaSpacing)
|
||||
) {
|
||||
gridItemsIndexed(groupMediaItems, key = { _, item -> item.key }) { index, mediaItem ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
.clip(RoundedCornerShape(0.dp))
|
||||
.background(if (isDarkTheme) Color(0xFF141416) else Color(0xFFE6E7EB))
|
||||
) {
|
||||
ImageAttachment(
|
||||
attachment = mediaItem.attachment,
|
||||
chachaKey = mediaItem.chachaKey,
|
||||
privateKey = currentUserPrivateKey,
|
||||
senderPublicKey = mediaItem.senderPublicKey,
|
||||
isOutgoing = mediaItem.senderPublicKey == currentUserPublicKey,
|
||||
isDarkTheme = isDarkTheme,
|
||||
timestamp = Date(mediaItem.timestamp),
|
||||
showTimeOverlay = false,
|
||||
fillMaxSize = true,
|
||||
onImageClick = { _, bounds ->
|
||||
imageViewerInitialIndex = index
|
||||
imageViewerSourceBounds = bounds
|
||||
showImageViewer = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SharedMediaFastScrollOverlay(
|
||||
visible = mediaFastScrollVisible,
|
||||
progress = mediaFastScrollProgress,
|
||||
monthLabel = mediaFastScrollMonthLabel,
|
||||
isDarkTheme = isDarkTheme,
|
||||
showHint = mediaFastScrollVisible && !groupMediaFastScrollHintDismissed,
|
||||
onHintDismissed = { groupMediaFastScrollHintDismissed = true },
|
||||
onDragProgressChanged = { fraction ->
|
||||
if (groupMediaItems.isEmpty()) return@SharedMediaFastScrollOverlay
|
||||
val targetRow = ((mediaRowsCount - 1) * fraction).roundToInt()
|
||||
val targetIndex = (targetRow * mediaColumns).coerceIn(0, groupMediaItems.lastIndex)
|
||||
scope.launch {
|
||||
groupMediaGridState.scrollToItem(targetIndex)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1868,6 +1919,12 @@ private fun shortPublicKey(publicKey: String): String {
|
||||
return "${trimmed.take(6)}...${trimmed.takeLast(4)}"
|
||||
}
|
||||
|
||||
private fun formatMediaMonthLabel(timestamp: Long): String {
|
||||
return runCatching {
|
||||
SimpleDateFormat("MMMM yyyy", Locale.getDefault()).format(Date(timestamp))
|
||||
}.getOrElse { "" }
|
||||
}
|
||||
|
||||
private fun decryptStoredMessageText(encryptedText: String, privateKey: String): String {
|
||||
if (encryptedText.isBlank()) return ""
|
||||
if (privateKey.isBlank()) return encryptedText
|
||||
|
||||
Reference in New Issue
Block a user