feat: Refactor attachment components to adopt Telegram-style UI for images and files
This commit is contained in:
@@ -90,6 +90,9 @@ dependencies {
|
||||
implementation("io.coil-kt:coil-compose:2.5.0")
|
||||
implementation("io.coil-kt:coil-gif:2.5.0") // For animated WebP/GIF support
|
||||
|
||||
// Blurhash for image placeholders
|
||||
implementation("com.vanniktech:blurhash:0.1.0")
|
||||
|
||||
// Crypto libraries for key generation
|
||||
implementation("org.bitcoinj:bitcoinj-core:0.16.2")
|
||||
implementation("org.bouncycastle:bcprov-jdk15to18:1.77")
|
||||
|
||||
@@ -105,7 +105,7 @@ fun MessageAttachments(
|
||||
}
|
||||
|
||||
/**
|
||||
* Image attachment
|
||||
* Image attachment - Telegram style
|
||||
*/
|
||||
@Composable
|
||||
fun ImageAttachment(
|
||||
@@ -114,26 +114,20 @@ fun ImageAttachment(
|
||||
privateKey: String,
|
||||
isDarkTheme: Boolean
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var downloadStatus by remember { mutableStateOf(DownloadStatus.PENDING) }
|
||||
var imageBitmap by remember { mutableStateOf<Bitmap?>(null) }
|
||||
var downloadProgress by remember { mutableStateOf(0) }
|
||||
|
||||
// Проверяем статус при загрузке
|
||||
LaunchedEffect(attachment.id) {
|
||||
downloadStatus = if (attachment.blob.isNotEmpty() && !isDownloadTag(attachment.preview)) {
|
||||
// Blob уже есть - сразу декодируем
|
||||
DownloadStatus.DOWNLOADED
|
||||
} else if (isDownloadTag(attachment.preview)) {
|
||||
// Нужно скачать с сервера
|
||||
DownloadStatus.NOT_DOWNLOADED
|
||||
} else {
|
||||
DownloadStatus.DOWNLOADED
|
||||
}
|
||||
|
||||
// Если скачано - декодируем изображение
|
||||
if (downloadStatus == DownloadStatus.DOWNLOADED && attachment.blob.isNotEmpty()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
imageBitmap = base64ToBitmap(attachment.blob)
|
||||
@@ -145,19 +139,15 @@ fun ImageAttachment(
|
||||
scope.launch {
|
||||
try {
|
||||
downloadStatus = DownloadStatus.DOWNLOADING
|
||||
|
||||
val tag = getDownloadTag(attachment.preview)
|
||||
if (tag.isEmpty()) {
|
||||
downloadStatus = DownloadStatus.ERROR
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Скачиваем с транспортного сервера
|
||||
val encryptedContent = TransportManager.downloadFile(attachment.id, tag)
|
||||
|
||||
downloadStatus = DownloadStatus.DECRYPTING
|
||||
|
||||
// Расшифровываем с ChaCha ключом
|
||||
val decrypted = MessageCrypto.decryptAttachmentBlob(
|
||||
encryptedContent,
|
||||
chachaKey,
|
||||
@@ -179,71 +169,64 @@ fun ImageAttachment(
|
||||
}
|
||||
}
|
||||
|
||||
Card(
|
||||
// Telegram-style: Изображение без Card wrapper
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.widthIn(min = 200.dp, max = 280.dp)
|
||||
.heightIn(min = 150.dp, max = 350.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable {
|
||||
if (downloadStatus == DownloadStatus.NOT_DOWNLOADED) {
|
||||
download()
|
||||
}
|
||||
},
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE8E8E8)
|
||||
)
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 100.dp, max = 300.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
when (downloadStatus) {
|
||||
DownloadStatus.DOWNLOADED -> {
|
||||
imageBitmap?.let { bitmap ->
|
||||
Image(
|
||||
bitmap = bitmap.asImageBitmap(),
|
||||
contentDescription = "Image",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentScale = ContentScale.Fit
|
||||
)
|
||||
}
|
||||
when (downloadStatus) {
|
||||
DownloadStatus.DOWNLOADED -> {
|
||||
imageBitmap?.let { bitmap ->
|
||||
Image(
|
||||
bitmap = bitmap.asImageBitmap(),
|
||||
contentDescription = "Image",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
}
|
||||
DownloadStatus.NOT_DOWNLOADED -> {
|
||||
// Показываем preview (blurhash) если есть
|
||||
}
|
||||
DownloadStatus.NOT_DOWNLOADED -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE0E0E0)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Image,
|
||||
Icons.Default.Download,
|
||||
contentDescription = null,
|
||||
tint = if (isDarkTheme) Color.White.copy(alpha = 0.7f) else Color.Gray,
|
||||
modifier = Modifier.size(48.dp)
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(40.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Button(
|
||||
onClick = download,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = PrimaryBlue
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Download,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Download")
|
||||
}
|
||||
Text(
|
||||
"Tap to download",
|
||||
fontSize = 13.sp,
|
||||
color = if (isDarkTheme) Color.White.copy(0.7f) else Color.Gray
|
||||
)
|
||||
}
|
||||
}
|
||||
DownloadStatus.DOWNLOADING, DownloadStatus.DECRYPTING -> {
|
||||
}
|
||||
DownloadStatus.DOWNLOADING, DownloadStatus.DECRYPTING -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE0E0E0)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
color = PrimaryBlue,
|
||||
@@ -253,32 +236,44 @@ fun ImageAttachment(
|
||||
Text(
|
||||
text = if (downloadStatus == DownloadStatus.DECRYPTING) "Decrypting..." else "Downloading...",
|
||||
fontSize = 12.sp,
|
||||
color = if (isDarkTheme) Color.White.copy(alpha = 0.7f) else Color.Gray
|
||||
color = if (isDarkTheme) Color.White.copy(0.7f) else Color.Gray
|
||||
)
|
||||
}
|
||||
}
|
||||
DownloadStatus.ERROR -> {
|
||||
}
|
||||
DownloadStatus.ERROR -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE0E0E0)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.padding(16.dp)
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Error,
|
||||
contentDescription = null,
|
||||
tint = Color.Red,
|
||||
tint = Color(0xFFE53935),
|
||||
modifier = Modifier.size(32.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text("Download failed", color = Color.Red, fontSize = 12.sp)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text("Failed", fontSize = 12.sp, color = Color(0xFFE53935))
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
TextButton(onClick = download) {
|
||||
Text("Retry")
|
||||
Text("Retry", fontSize = 12.sp, color = PrimaryBlue)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
CircularProgressIndicator(modifier = Modifier.size(24.dp))
|
||||
}
|
||||
else -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE0E0E0)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(24.dp), color = PrimaryBlue)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,7 +281,7 @@ fun ImageAttachment(
|
||||
}
|
||||
|
||||
/**
|
||||
* File attachment
|
||||
* File attachment - Telegram style
|
||||
*/
|
||||
@Composable
|
||||
fun FileAttachment(
|
||||
@@ -298,12 +293,10 @@ fun FileAttachment(
|
||||
val scope = rememberCoroutineScope()
|
||||
var downloadStatus by remember { mutableStateOf(DownloadStatus.PENDING) }
|
||||
|
||||
// Парсим метаданные: "filesize::filename"
|
||||
val preview = attachment.preview
|
||||
val parts = preview.split("::")
|
||||
val (fileSize, fileName) = when {
|
||||
parts.size >= 3 -> {
|
||||
// Формат: "UUID::filesize::filename"
|
||||
val size = parts[1].toLongOrNull() ?: 0L
|
||||
val name = parts.drop(2).joinToString("::")
|
||||
Pair(size, name)
|
||||
@@ -315,7 +308,6 @@ fun FileAttachment(
|
||||
else -> Pair(0L, "File")
|
||||
}
|
||||
|
||||
// Проверяем статус
|
||||
LaunchedEffect(attachment.id) {
|
||||
downloadStatus = if (isDownloadTag(preview)) {
|
||||
DownloadStatus.NOT_DOWNLOADED
|
||||
@@ -328,7 +320,6 @@ fun FileAttachment(
|
||||
scope.launch {
|
||||
try {
|
||||
downloadStatus = DownloadStatus.DOWNLOADING
|
||||
|
||||
val tag = getDownloadTag(preview)
|
||||
if (tag.isEmpty()) {
|
||||
downloadStatus = DownloadStatus.ERROR
|
||||
@@ -336,7 +327,6 @@ fun FileAttachment(
|
||||
}
|
||||
|
||||
val encryptedContent = TransportManager.downloadFile(attachment.id, tag)
|
||||
|
||||
downloadStatus = DownloadStatus.DECRYPTING
|
||||
|
||||
val decrypted = MessageCrypto.decryptAttachmentBlob(
|
||||
@@ -346,7 +336,6 @@ fun FileAttachment(
|
||||
)
|
||||
|
||||
if (decrypted != null) {
|
||||
// TODO: Сохранить файл в Downloads
|
||||
downloadStatus = DownloadStatus.DOWNLOADED
|
||||
} else {
|
||||
downloadStatus = DownloadStatus.ERROR
|
||||
@@ -358,102 +347,97 @@ fun FileAttachment(
|
||||
}
|
||||
}
|
||||
|
||||
Card(
|
||||
// Telegram-style: компактный row с иконкой файла
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(if (isDarkTheme) Color(0xFF1F1F1F) else Color.White.copy(0.3f))
|
||||
.clickable {
|
||||
if (downloadStatus == DownloadStatus.NOT_DOWNLOADED) {
|
||||
download()
|
||||
}
|
||||
},
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE8E8E8)
|
||||
)
|
||||
}
|
||||
.padding(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
// File icon
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
.size(44.dp)
|
||||
.clip(RoundedCornerShape(6.dp))
|
||||
.background(PrimaryBlue.copy(alpha = 0.15f)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
// File icon
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(PrimaryBlue.copy(alpha = 0.2f)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.InsertDriveFile,
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
||||
// File info
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = fileName,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = if (isDarkTheme) Color.White else Color.Black,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = formatFileSize(fileSize),
|
||||
fontSize = 12.sp,
|
||||
color = if (isDarkTheme) Color.White.copy(alpha = 0.5f) else Color.Gray
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
// Download/status icon
|
||||
when (downloadStatus) {
|
||||
DownloadStatus.NOT_DOWNLOADED -> {
|
||||
Icon(
|
||||
Icons.Default.InsertDriveFile,
|
||||
contentDescription = null,
|
||||
Icons.Default.Download,
|
||||
contentDescription = "Download",
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
// File info
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = fileName,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = if (isDarkTheme) Color.White else Color.Black,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = formatFileSize(fileSize),
|
||||
fontSize = 12.sp,
|
||||
color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Gray
|
||||
DownloadStatus.DOWNLOADING, DownloadStatus.DECRYPTING -> {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(20.dp),
|
||||
color = PrimaryBlue,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
}
|
||||
|
||||
// Download button/status
|
||||
when (downloadStatus) {
|
||||
DownloadStatus.NOT_DOWNLOADED -> {
|
||||
IconButton(onClick = download) {
|
||||
Icon(
|
||||
Icons.Default.Download,
|
||||
contentDescription = "Download",
|
||||
tint = PrimaryBlue
|
||||
)
|
||||
}
|
||||
}
|
||||
DownloadStatus.DOWNLOADING, DownloadStatus.DECRYPTING -> {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = PrimaryBlue,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
}
|
||||
DownloadStatus.DOWNLOADED -> {
|
||||
Icon(
|
||||
Icons.Default.CheckCircle,
|
||||
contentDescription = "Downloaded",
|
||||
tint = Color(0xFF4CAF50)
|
||||
)
|
||||
}
|
||||
DownloadStatus.ERROR -> {
|
||||
IconButton(onClick = download) {
|
||||
Icon(
|
||||
Icons.Default.Refresh,
|
||||
contentDescription = "Retry",
|
||||
tint = Color.Red
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
DownloadStatus.DOWNLOADED -> {
|
||||
Icon(
|
||||
Icons.Default.CheckCircle,
|
||||
contentDescription = "Downloaded",
|
||||
tint = Color(0xFF4CAF50),
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
DownloadStatus.ERROR -> {
|
||||
Icon(
|
||||
Icons.Default.Refresh,
|
||||
contentDescription = "Retry",
|
||||
tint = Color(0xFFE53935),
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avatar attachment - аватар отправителя
|
||||
* Avatar attachment - Telegram style (более компактный)
|
||||
*/
|
||||
@Composable
|
||||
fun AvatarAttachment(
|
||||
@@ -464,7 +448,6 @@ fun AvatarAttachment(
|
||||
avatarRepository: AvatarRepository?,
|
||||
isDarkTheme: Boolean
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
var downloadStatus by remember { mutableStateOf(DownloadStatus.PENDING) }
|
||||
@@ -490,7 +473,6 @@ fun AvatarAttachment(
|
||||
scope.launch {
|
||||
try {
|
||||
downloadStatus = DownloadStatus.DOWNLOADING
|
||||
|
||||
val tag = getDownloadTag(attachment.preview)
|
||||
if (tag.isEmpty()) {
|
||||
downloadStatus = DownloadStatus.ERROR
|
||||
@@ -498,7 +480,6 @@ fun AvatarAttachment(
|
||||
}
|
||||
|
||||
val encryptedContent = TransportManager.downloadFile(attachment.id, tag)
|
||||
|
||||
downloadStatus = DownloadStatus.DECRYPTING
|
||||
|
||||
val decrypted = MessageCrypto.decryptAttachmentBlob(
|
||||
@@ -511,7 +492,6 @@ fun AvatarAttachment(
|
||||
withContext(Dispatchers.IO) {
|
||||
avatarBitmap = base64ToBitmap(decrypted)
|
||||
}
|
||||
// Сохраняем аватар в кэш
|
||||
avatarRepository?.saveAvatar(senderPublicKey, decrypted)
|
||||
downloadStatus = DownloadStatus.DOWNLOADED
|
||||
} else {
|
||||
@@ -524,99 +504,107 @@ fun AvatarAttachment(
|
||||
}
|
||||
}
|
||||
|
||||
Card(
|
||||
// Telegram-style: компактный row с круглым превью
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(12.dp)),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE8E8E8)
|
||||
)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(if (isDarkTheme) Color(0xFF1F1F1F) else Color.White.copy(0.3f))
|
||||
.clickable(enabled = downloadStatus == DownloadStatus.NOT_DOWNLOADED) {
|
||||
download()
|
||||
}
|
||||
.padding(10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
// Avatar preview (круглое)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
.size(50.dp)
|
||||
.clip(CircleShape)
|
||||
.background(if (isDarkTheme) Color(0xFF2A2A2A) else Color(0xFFE0E0E0)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
// Avatar preview
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(60.dp)
|
||||
.clip(CircleShape)
|
||||
.background(if (isDarkTheme) Color(0xFF3A3A3A) else Color(0xFFD0D0D0)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
when (downloadStatus) {
|
||||
DownloadStatus.DOWNLOADED -> {
|
||||
avatarBitmap?.let { bitmap ->
|
||||
Image(
|
||||
bitmap = bitmap.asImageBitmap(),
|
||||
contentDescription = "Avatar",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
}
|
||||
}
|
||||
DownloadStatus.DOWNLOADING, DownloadStatus.DECRYPTING -> {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = PrimaryBlue,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
Icon(
|
||||
Icons.Default.Person,
|
||||
contentDescription = null,
|
||||
tint = if (isDarkTheme) Color.White.copy(alpha = 0.5f) else Color.Gray,
|
||||
modifier = Modifier.size(32.dp)
|
||||
when (downloadStatus) {
|
||||
DownloadStatus.DOWNLOADED -> {
|
||||
avatarBitmap?.let { bitmap ->
|
||||
Image(
|
||||
bitmap = bitmap.asImageBitmap(),
|
||||
contentDescription = "Avatar",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
}
|
||||
}
|
||||
DownloadStatus.DOWNLOADING, DownloadStatus.DECRYPTING -> {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(20.dp),
|
||||
color = PrimaryBlue,
|
||||
strokeWidth = 2.dp
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
Icon(
|
||||
Icons.Default.Person,
|
||||
contentDescription = null,
|
||||
tint = if (isDarkTheme) Color.White.copy(0.4f) else Color.Gray,
|
||||
modifier = Modifier.size(28.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = "Avatar",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = if (isDarkTheme) Color.White else Color.Black
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Icon(
|
||||
Icons.Default.Lock,
|
||||
contentDescription = null,
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(14.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = "An avatar image shared in the message.",
|
||||
fontSize = 12.sp,
|
||||
color = if (isDarkTheme) Color.White.copy(alpha = 0.6f) else Color.Gray
|
||||
text = "Avatar",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = if (isDarkTheme) Color.White else Color.Black
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Icon(
|
||||
Icons.Default.Lock,
|
||||
contentDescription = null,
|
||||
tint = if (isDarkTheme) Color.White.copy(0.5f) else Color.Gray,
|
||||
modifier = Modifier.size(12.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (downloadStatus == DownloadStatus.NOT_DOWNLOADED) {
|
||||
Button(
|
||||
onClick = download,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = PrimaryBlue
|
||||
),
|
||||
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 6.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Download,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text("Download", fontSize = 12.sp)
|
||||
}
|
||||
Text(
|
||||
text = "Profile photo shared",
|
||||
fontSize = 12.sp,
|
||||
color = if (isDarkTheme) Color.White.copy(0.5f) else Color.Gray
|
||||
)
|
||||
}
|
||||
|
||||
// Download/status icon
|
||||
when (downloadStatus) {
|
||||
DownloadStatus.NOT_DOWNLOADED -> {
|
||||
Icon(
|
||||
Icons.Default.Download,
|
||||
contentDescription = "Download",
|
||||
tint = PrimaryBlue,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
DownloadStatus.DOWNLOADED -> {
|
||||
Icon(
|
||||
Icons.Default.CheckCircle,
|
||||
contentDescription = "Downloaded",
|
||||
tint = Color(0xFF4CAF50),
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
DownloadStatus.ERROR -> {
|
||||
Icon(
|
||||
Icons.Default.Refresh,
|
||||
contentDescription = "Retry",
|
||||
tint = Color(0xFFE53935),
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user