feat: Add delivery confirmation for incoming messages in ProtocolManager
This commit is contained in:
@@ -26,6 +26,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
@@ -907,7 +908,7 @@ fun ChatDetailScreen(
|
||||
}
|
||||
|
||||
// 🔥 SELECTION ACTION BAR - Reply/Forward (появляется при выборе сообщений)
|
||||
// Стеклянный стиль как у инпута
|
||||
// Стеклянный стиль как у инпута с блюром
|
||||
AnimatedVisibility(
|
||||
visible = isSelectionMode,
|
||||
enter = fadeIn(tween(200)) + slideInVertically(initialOffsetY = { it }),
|
||||
@@ -915,40 +916,46 @@ fun ChatDetailScreen(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
// Glass container
|
||||
// Glass container с эффектом блюра
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(20.dp))
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.background(
|
||||
if (isDarkTheme) Color(0xFF2A2A2A).copy(alpha = 0.85f)
|
||||
else Color(0xFFF2F3F5).copy(alpha = 0.92f)
|
||||
if (isDarkTheme) Color(0xFF1C1C1E).copy(alpha = 0.7f)
|
||||
else Color(0xFFF8F9FA).copy(alpha = 0.8f)
|
||||
)
|
||||
.border(
|
||||
width = 0.5.dp,
|
||||
color = if (isDarkTheme) Color.White.copy(alpha = 0.08f)
|
||||
else Color.Black.copy(alpha = 0.06f),
|
||||
shape = RoundedCornerShape(20.dp)
|
||||
width = 1.dp,
|
||||
color = if (isDarkTheme) Color.White.copy(alpha = 0.12f)
|
||||
else Color.Black.copy(alpha = 0.08f),
|
||||
shape = RoundedCornerShape(24.dp)
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp, horizontal = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
.padding(vertical = 10.dp, horizontal = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Reply button - стеклянная кнопка
|
||||
// Reply button - стеклянная кнопка с блюром
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.clip(RoundedCornerShape(14.dp))
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(
|
||||
if (isDarkTheme) Color.White.copy(alpha = 0.08f)
|
||||
else Color.Black.copy(alpha = 0.04f)
|
||||
if (isDarkTheme) Color.White.copy(alpha = 0.1f)
|
||||
else Color.Black.copy(alpha = 0.06f)
|
||||
)
|
||||
.border(
|
||||
width = 0.5.dp,
|
||||
color = if (isDarkTheme) Color.White.copy(alpha = 0.15f)
|
||||
else Color.Black.copy(alpha = 0.1f),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
.clickable {
|
||||
val selectedMsgs = messages
|
||||
@@ -957,7 +964,7 @@ fun ChatDetailScreen(
|
||||
viewModel.setReplyMessages(selectedMsgs)
|
||||
selectedMessages = emptySet()
|
||||
}
|
||||
.padding(vertical = 12.dp),
|
||||
.padding(vertical = 14.dp, horizontal = 16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Row(
|
||||
@@ -975,19 +982,25 @@ fun ChatDetailScreen(
|
||||
"Reply",
|
||||
color = PrimaryBlue,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Forward button - стеклянная кнопка
|
||||
// Forward button - стеклянная кнопка с блюром
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.clip(RoundedCornerShape(14.dp))
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(
|
||||
if (isDarkTheme) Color.White.copy(alpha = 0.08f)
|
||||
else Color.Black.copy(alpha = 0.04f)
|
||||
if (isDarkTheme) Color.White.copy(alpha = 0.1f)
|
||||
else Color.Black.copy(alpha = 0.06f)
|
||||
)
|
||||
.border(
|
||||
width = 0.5.dp,
|
||||
color = if (isDarkTheme) Color.White.copy(alpha = 0.15f)
|
||||
else Color.Black.copy(alpha = 0.1f),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
.clickable {
|
||||
val selectedMsgs = messages
|
||||
@@ -996,7 +1009,7 @@ fun ChatDetailScreen(
|
||||
viewModel.setForwardMessages(selectedMsgs)
|
||||
selectedMessages = emptySet()
|
||||
}
|
||||
.padding(vertical = 12.dp),
|
||||
.padding(vertical = 14.dp, horizontal = 16.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Row(
|
||||
@@ -1014,7 +1027,7 @@ fun ChatDetailScreen(
|
||||
"Forward",
|
||||
color = PrimaryBlue,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Medium
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1283,19 +1296,9 @@ private fun MessageBubble(
|
||||
)
|
||||
if (message.isOutgoing) {
|
||||
Spacer(modifier = Modifier.width(3.dp))
|
||||
Icon(
|
||||
when (message.status) {
|
||||
MessageStatus.SENDING -> Icons.Default.Schedule
|
||||
MessageStatus.SENT -> Icons.Default.Done
|
||||
MessageStatus.DELIVERED -> Icons.Default.DoneAll
|
||||
MessageStatus.READ -> Icons.Default.DoneAll
|
||||
},
|
||||
contentDescription = null,
|
||||
tint =
|
||||
if (message.status == MessageStatus.READ)
|
||||
Color(0xFF4FC3F7) // Голубые галочки как в Telegram
|
||||
else timeColor,
|
||||
modifier = Modifier.size(16.dp)
|
||||
AnimatedMessageStatus(
|
||||
status = message.status,
|
||||
timeColor = timeColor
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1304,6 +1307,65 @@ private fun MessageBubble(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🎯 Анимированный статус сообщения с плавными переходами
|
||||
*/
|
||||
@Composable
|
||||
private fun AnimatedMessageStatus(
|
||||
status: MessageStatus,
|
||||
timeColor: Color
|
||||
) {
|
||||
// Цвет с анимацией
|
||||
val targetColor = if (status == MessageStatus.READ) Color(0xFF4FC3F7) else timeColor
|
||||
val animatedColor by animateColorAsState(
|
||||
targetValue = targetColor,
|
||||
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing),
|
||||
label = "statusColor"
|
||||
)
|
||||
|
||||
// Анимация scale для эффекта "pop"
|
||||
var previousStatus by remember { mutableStateOf(status) }
|
||||
var shouldAnimate by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(status) {
|
||||
if (previousStatus != status) {
|
||||
shouldAnimate = true
|
||||
previousStatus = status
|
||||
}
|
||||
}
|
||||
|
||||
val scale by animateFloatAsState(
|
||||
targetValue = if (shouldAnimate) 1.2f else 1f,
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioMediumBouncy,
|
||||
stiffness = Spring.StiffnessLow
|
||||
),
|
||||
finishedListener = { shouldAnimate = false },
|
||||
label = "statusScale"
|
||||
)
|
||||
|
||||
// Crossfade для плавной смены иконки
|
||||
Crossfade(
|
||||
targetState = status,
|
||||
animationSpec = tween(durationMillis = 200),
|
||||
label = "statusIcon"
|
||||
) { currentStatus ->
|
||||
Icon(
|
||||
imageVector = when (currentStatus) {
|
||||
MessageStatus.SENDING -> Icons.Default.Schedule
|
||||
MessageStatus.SENT -> Icons.Default.Done
|
||||
MessageStatus.DELIVERED -> Icons.Default.DoneAll
|
||||
MessageStatus.READ -> Icons.Default.DoneAll
|
||||
},
|
||||
contentDescription = null,
|
||||
tint = animatedColor,
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
.scale(scale)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** 🚀 Разделитель даты с fade-in анимацией */
|
||||
@Composable
|
||||
private fun DateHeader(dateText: String, secondaryTextColor: Color) {
|
||||
|
||||
@@ -226,12 +226,14 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
// Обновляем диалог
|
||||
saveDialog(decryptedText, packet.timestamp)
|
||||
|
||||
// Отправляем подтверждение доставки
|
||||
val deliveryPacket = PacketDelivery().apply {
|
||||
toPublicKey = packet.fromPublicKey
|
||||
messageId = packet.messageId
|
||||
// ⚠️ Delivery отправляется в ProtocolManager.setupPacketHandlers()
|
||||
// Не отправляем повторно чтобы избежать дублирования!
|
||||
|
||||
// 👁️ Сразу отправляем read receipt (как в Telegram - сообщения прочитаны если чат открыт)
|
||||
delay(100) // Небольшая задержка для естественности
|
||||
withContext(Dispatchers.Main) {
|
||||
sendReadReceipt(packet.messageId, packet.fromPublicKey)
|
||||
}
|
||||
ProtocolManager.send(deliveryPacket)
|
||||
|
||||
} catch (e: Exception) {
|
||||
ProtocolManager.addLog("❌ Error handling incoming message: ${e.message}")
|
||||
|
||||
@@ -16,17 +16,20 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.airbnb.lottie.compose.*
|
||||
import com.rosetta.messenger.R
|
||||
import com.rosetta.messenger.network.SearchUser
|
||||
import com.rosetta.messenger.ui.components.VerifiedBadge
|
||||
import com.rosetta.messenger.ui.onboarding.PrimaryBlue
|
||||
|
||||
/**
|
||||
* Компонент отображения результатов поиска пользователей Аналогичен результатам поиска в React
|
||||
* Native приложении
|
||||
* Компонент отображения результатов поиска пользователей
|
||||
* Минималистичный дизайн с плавными анимациями
|
||||
*/
|
||||
@Composable
|
||||
fun SearchResultsList(
|
||||
@@ -41,64 +44,122 @@ fun SearchResultsList(
|
||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
// Loading state
|
||||
AnimatedVisibility(
|
||||
visible = isSearching,
|
||||
enter = fadeIn(animationSpec = tween(durationMillis = 200)),
|
||||
exit = fadeOut(animationSpec = tween(durationMillis = 200))
|
||||
enter = fadeIn(animationSpec = tween(200)),
|
||||
exit = fadeOut(animationSpec = tween(150))
|
||||
) {
|
||||
// Индикатор загрузки в центре
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding(), // Поднимается при клавиатуре
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(32.dp),
|
||||
color = if (isDarkTheme) Color(0xFF9E9E9E) else PrimaryBlue,
|
||||
strokeWidth = 3.dp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(text = "Searching...", fontSize = 14.sp, color = secondaryTextColor)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(32.dp),
|
||||
color = PrimaryBlue,
|
||||
strokeWidth = 2.5.dp
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = "Searching...",
|
||||
fontSize = 14.sp,
|
||||
color = secondaryTextColor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Empty state - поднимается при открытии клавиатуры
|
||||
AnimatedVisibility(
|
||||
visible = !isSearching && searchResults.isEmpty(),
|
||||
enter = fadeIn(animationSpec = tween(durationMillis = 300)),
|
||||
exit = fadeOut(animationSpec = tween(durationMillis = 200))
|
||||
enter = fadeIn(animationSpec = tween(300)),
|
||||
exit = fadeOut(animationSpec = tween(200))
|
||||
) {
|
||||
// Подсказка в центре экрана
|
||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
text = "Search by username or public key",
|
||||
fontSize = 15.sp,
|
||||
color = secondaryTextColor
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding(), // Поднимается при клавиатуре
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
// Lottie search animation
|
||||
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.search))
|
||||
val progress by animateLottieCompositionAsState(
|
||||
composition = composition,
|
||||
iterations = LottieConstants.IterateForever
|
||||
)
|
||||
|
||||
LottieAnimation(
|
||||
composition = composition,
|
||||
progress = { progress },
|
||||
modifier = Modifier.size(120.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Text(
|
||||
text = "Search for users",
|
||||
fontSize = 17.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = textColor
|
||||
)
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = "Enter username or public key",
|
||||
fontSize = 14.sp,
|
||||
color = secondaryTextColor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Results list with staggered animation
|
||||
AnimatedVisibility(
|
||||
visible = !isSearching && searchResults.isNotEmpty(),
|
||||
enter = fadeIn(animationSpec = tween(durationMillis = 300)),
|
||||
exit = fadeOut(animationSpec = tween(durationMillis = 200))
|
||||
enter = fadeIn(animationSpec = tween(250)),
|
||||
exit = fadeOut(animationSpec = tween(150))
|
||||
) {
|
||||
// Список результатов без серой плашки
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentPadding = PaddingValues(vertical = 4.dp)
|
||||
) {
|
||||
itemsIndexed(searchResults) { index, user ->
|
||||
SearchResultItem(
|
||||
user = user,
|
||||
isOwnAccount = user.publicKey == currentUserPublicKey,
|
||||
isDarkTheme = isDarkTheme,
|
||||
isLastItem = index == searchResults.size - 1,
|
||||
onClick = { onUserClick(user) }
|
||||
)
|
||||
// Staggered animation
|
||||
var isVisible by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) {
|
||||
kotlinx.coroutines.delay(index * 40L)
|
||||
isVisible = true
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = isVisible,
|
||||
enter = fadeIn(tween(200)) + slideInHorizontally(
|
||||
initialOffsetX = { it / 3 },
|
||||
animationSpec = tween(250, easing = FastOutSlowInEasing)
|
||||
),
|
||||
exit = fadeOut(tween(100))
|
||||
) {
|
||||
SearchResultItem(
|
||||
user = user,
|
||||
isOwnAccount = user.publicKey == currentUserPublicKey,
|
||||
isDarkTheme = isDarkTheme,
|
||||
isLastItem = index == searchResults.size - 1,
|
||||
onClick = { onUserClick(user) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Элемент результата поиска - пользователь */
|
||||
/** Элемент результата поиска - минималистичный дизайн */
|
||||
@Composable
|
||||
private fun SearchResultItem(
|
||||
user: SearchUser,
|
||||
@@ -109,32 +170,28 @@ private fun SearchResultItem(
|
||||
) {
|
||||
val textColor = if (isDarkTheme) Color.White else Color.Black
|
||||
val secondaryTextColor = if (isDarkTheme) Color(0xFF8E8E93) else Color(0xFF666666)
|
||||
val dividerColor = if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFE8E8E8)
|
||||
val dividerColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE8E8E8)
|
||||
|
||||
// Получаем цвета аватара - используем publicKey для консистентности
|
||||
val avatarColors =
|
||||
getAvatarColor(
|
||||
if (isOwnAccount) "SavedMessages" else user.publicKey,
|
||||
isDarkTheme
|
||||
)
|
||||
// Получаем цвета аватара
|
||||
val avatarColors = getAvatarColor(
|
||||
if (isOwnAccount) "SavedMessages" else user.publicKey,
|
||||
isDarkTheme
|
||||
)
|
||||
|
||||
Column {
|
||||
Row(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// Аватар
|
||||
// Avatar - clean and simple
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(
|
||||
if (isOwnAccount) PrimaryBlue
|
||||
else avatarColors.backgroundColor
|
||||
),
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.background(if (isOwnAccount) PrimaryBlue else avatarColors.backgroundColor),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (isOwnAccount) {
|
||||
@@ -146,14 +203,13 @@ private fun SearchResultItem(
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text =
|
||||
if (user.title.isNotEmpty()) {
|
||||
text = if (user.title.isNotEmpty()) {
|
||||
getInitials(user.title)
|
||||
} else {
|
||||
user.publicKey.take(2).uppercase()
|
||||
},
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
color = avatarColors.textColor
|
||||
)
|
||||
}
|
||||
@@ -161,28 +217,23 @@ private fun SearchResultItem(
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
// Информация о пользователе
|
||||
// User info
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
// Имя и значок верификации
|
||||
// Name and verification badge
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text =
|
||||
if (isOwnAccount) {
|
||||
"Saved Messages"
|
||||
} else {
|
||||
user.title.ifEmpty { user.publicKey.take(10) }
|
||||
},
|
||||
text = if (isOwnAccount) "Saved Messages"
|
||||
else user.title.ifEmpty { user.publicKey.take(10) },
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = textColor,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
// Значок верификации
|
||||
if (!isOwnAccount && user.verified > 0) {
|
||||
VerifiedBadge(verified = user.verified, size = 16)
|
||||
}
|
||||
@@ -190,14 +241,10 @@ private fun SearchResultItem(
|
||||
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
|
||||
// Юзернейм или публичный ключ
|
||||
// Username
|
||||
Text(
|
||||
text =
|
||||
if (isOwnAccount) {
|
||||
"Notes"
|
||||
} else {
|
||||
"@${user.username.ifEmpty { user.publicKey.take(10) + "..." }}"
|
||||
},
|
||||
text = if (isOwnAccount) "Notes"
|
||||
else "@${user.username.ifEmpty { user.publicKey.take(10) + "..." }}",
|
||||
fontSize = 14.sp,
|
||||
color = secondaryTextColor,
|
||||
maxLines = 1,
|
||||
@@ -206,12 +253,12 @@ private fun SearchResultItem(
|
||||
}
|
||||
}
|
||||
|
||||
// Разделитель между элементами
|
||||
// Simple divider
|
||||
if (!isLastItem) {
|
||||
Divider(
|
||||
modifier = Modifier.padding(start = 80.dp),
|
||||
color = dividerColor,
|
||||
thickness = 0.5.dp
|
||||
modifier = Modifier.padding(start = 76.dp),
|
||||
color = dividerColor,
|
||||
thickness = 0.5.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user