package io.nexilis.alpha.ui.components import android.graphics.BitmapFactory import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.DownloadForOffline import androidx.compose.material.icons.filled.Headset import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.toUpperCase import androidx.compose.ui.unit.dp import androidx.core.content.FileProvider import androidx.exifinterface.media.ExifInterface import androidx.hilt.navigation.compose.hiltViewModel import coil.compose.rememberAsyncImagePainter import io.nexilis.alpha.R import io.nexilis.service.core.Orange import io.nexilis.service.core.extension import io.nexilis.service.core.toHumanStandard import io.nexilis.service.data.entities.Message import io.nexilis.service.data.viewmodels.ProgressViewModel import java.io.File import java.io.FileInputStream @Composable fun ContentChat(modifier: Modifier = Modifier, message: Message) { val context = LocalContext.current if (message.image_id.isNotEmpty()) { Column(modifier = Modifier.padding(2.dp)) { val file = File(context.filesDir, message.image_id) val uri = FileProvider.getUriForFile( context, context.applicationContext.packageName + ".provider", file ) var width: Int var height: Int FileInputStream(file).use { val exif = ExifInterface(it) width = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 1) height = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 1) val orientation = exif.getAttributeInt( ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED ) if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270) { height = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 1) width = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 1) } } if (width == 0) { FileInputStream(file).use { val bitmap = BitmapFactory.decodeStream(it) width = bitmap.width if (height == 0) height = bitmap.height } } val aspectRatio = width.toFloat() / height.toFloat() Image( painter = rememberAsyncImagePainter(uri), contentDescription = "", modifier = modifier .aspectRatio(aspectRatio) .graphicsLayer { shape = RoundedCornerShape(12.dp) clip = true }, contentScale = ContentScale.Fit ) if (message.message_text.isNotEmpty()) { Text( text = message.message_text, modifier = Modifier.padding(8.dp), color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyMedium, ) } } } else if (message.file_id.isNotEmpty()) { Column( modifier = Modifier .padding(2.dp) .fillMaxWidth(0.65f) ) { Row(modifier = Modifier .graphicsLayer { shape = RoundedCornerShape(12.dp) clip = true } .align(Alignment.CenterHorizontally) .background(MaterialTheme.colorScheme.surface) .padding(8.dp) ) { FileType( message = message, modifier = Modifier .align(Alignment.CenterVertically) ) Column( modifier = Modifier .weight(1f) ) { Text( text = message.message_text.split("|").first(), color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyMedium, maxLines = 2, overflow = TextOverflow.Ellipsis ) FileDesc(file = message.file_id) } FileProgress( message = message, modifier = Modifier.align(Alignment.CenterVertically) ) } val messageText = message.message_text.split("|").last() if (messageText.isNotEmpty()) { Text( text = messageText, modifier = Modifier.padding(8.dp), color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyMedium, ) } } } else if (message.audio_id.isNotEmpty()) { Column( modifier = Modifier .padding(2.dp) .widthIn(max = 210.dp) ) { Row(modifier = Modifier .graphicsLayer { shape = RoundedCornerShape(12.dp) clip = true } .background(MaterialTheme.colorScheme.surface)) { Icon( modifier = Modifier .size(48.dp) .padding(start = 8.dp) .align(Alignment.CenterVertically), imageVector = Icons.Default.Headset, contentDescription = null, tint = Color.Orange ) Column { Row( modifier = Modifier.padding( start = 4.dp, top = 8.dp, end = 8.dp, bottom = 8.dp ) ) { Icon( modifier = Modifier .size(32.dp) .align(Alignment.CenterVertically), imageVector = Icons.Default.PlayArrow, contentDescription = null, tint = MaterialTheme.colorScheme.primary ) LinearProgressIndicator( progress = { 0.5f }, modifier = Modifier .align(Alignment.CenterVertically) .fillMaxWidth(), ) } Text( modifier = Modifier.padding( start = 4.dp, top = 8.dp, end = 8.dp, bottom = 8.dp ), color = MaterialTheme.colorScheme.onSurface, text = message.message_text.split("|").first(), style = MaterialTheme.typography.labelSmall, maxLines = 1, overflow = TextOverflow.Ellipsis ) } } val messageText = message.message_text.split("|").last() if (messageText.isNotEmpty()) { Text( text = messageText, modifier = Modifier.padding(8.dp), color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyMedium, ) } } } else { Text( text = message.message_text, modifier = Modifier.padding(8.dp), color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.bodyMedium, ) } } @Composable fun FileType(message: Message, modifier: Modifier) { val imageVector = when (message.file_id.extension) { "apk", "aab" -> ImageVector.vectorResource(id = R.drawable.ic_apk) "aac", "mp3", "m4a", "ogg" -> ImageVector.vectorResource(id = R.drawable.ic_audio) "xls", "xlsx" -> ImageVector.vectorResource(id = R.drawable.ic_excel) "jpg", "jpeg", "webp", "png", "svg" -> ImageVector.vectorResource(id = R.drawable.ic_image) "pdf" -> ImageVector.vectorResource(id = R.drawable.ic_pdf) "ppt", "pptx" -> ImageVector.vectorResource(id = R.drawable.ic_ppt) "txt" -> ImageVector.vectorResource(id = R.drawable.ic_txt) "mp4", "mov", "3gp" -> ImageVector.vectorResource(id = R.drawable.ic_video) "doc", "docx" -> ImageVector.vectorResource(id = R.drawable.ic_word) "zip", "gz", "rar" -> ImageVector.vectorResource(id = R.drawable.ic_zip) else -> ImageVector.vectorResource(id = R.drawable.ic_file) } Image( modifier = modifier .size(48.dp), imageVector = imageVector, contentDescription = null ) } @Composable fun FileDesc(file: String, modifier: Modifier = Modifier) { val context = LocalContext.current val f = File(context.filesDir, file) if (f.exists()) { Text( modifier = modifier, text = "${ f.length().toHumanStandard() } • ${file.extension.toUpperCase(Locale.current)}", color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.labelSmall, ) } } @Composable fun FileProgress(message: Message, modifier: Modifier = Modifier) { val context = LocalContext.current val progressViewModel: ProgressViewModel = hiltViewModel() val progress by progressViewModel.get(message.message_id).observeAsState() progress?.let { if (it.value > 0f && it.value < 1f) { CircularProgressIndicator( progress = { it.value }, modifier = modifier .size(32.dp), ) } } val file = File(context.filesDir, message.file_id) if (!file.exists()) { IconButton( onClick = { /*TODO*/ }, modifier = modifier ) { Icon( imageVector = Icons.Default.DownloadForOffline, contentDescription = null, tint = Color.LightGray ) } } }