ContentChat.kt 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. package io.nexilis.alpha.ui.components
  2. import android.graphics.BitmapFactory
  3. import androidx.compose.foundation.Image
  4. import androidx.compose.foundation.background
  5. import androidx.compose.foundation.layout.Column
  6. import androidx.compose.foundation.layout.Row
  7. import androidx.compose.foundation.layout.aspectRatio
  8. import androidx.compose.foundation.layout.fillMaxWidth
  9. import androidx.compose.foundation.layout.padding
  10. import androidx.compose.foundation.layout.size
  11. import androidx.compose.foundation.layout.widthIn
  12. import androidx.compose.foundation.shape.RoundedCornerShape
  13. import androidx.compose.material.icons.Icons
  14. import androidx.compose.material.icons.filled.DownloadForOffline
  15. import androidx.compose.material.icons.filled.Headset
  16. import androidx.compose.material.icons.filled.PlayArrow
  17. import androidx.compose.material3.CircularProgressIndicator
  18. import androidx.compose.material3.Icon
  19. import androidx.compose.material3.IconButton
  20. import androidx.compose.material3.LinearProgressIndicator
  21. import androidx.compose.material3.ListItem
  22. import androidx.compose.material3.ListItemColors
  23. import androidx.compose.material3.ListItemDefaults
  24. import androidx.compose.material3.MaterialTheme
  25. import androidx.compose.material3.Text
  26. import androidx.compose.runtime.Composable
  27. import androidx.compose.runtime.getValue
  28. import androidx.compose.runtime.livedata.observeAsState
  29. import androidx.compose.ui.Alignment
  30. import androidx.compose.ui.Modifier
  31. import androidx.compose.ui.graphics.Color
  32. import androidx.compose.ui.graphics.graphicsLayer
  33. import androidx.compose.ui.graphics.vector.ImageVector
  34. import androidx.compose.ui.layout.ContentScale
  35. import androidx.compose.ui.platform.LocalContext
  36. import androidx.compose.ui.res.vectorResource
  37. import androidx.compose.ui.text.intl.Locale
  38. import androidx.compose.ui.text.style.TextOverflow
  39. import androidx.compose.ui.text.toUpperCase
  40. import androidx.compose.ui.unit.dp
  41. import androidx.core.content.FileProvider
  42. import androidx.exifinterface.media.ExifInterface
  43. import androidx.hilt.navigation.compose.hiltViewModel
  44. import coil.compose.rememberAsyncImagePainter
  45. import io.nexilis.alpha.R
  46. import io.nexilis.service.core.Orange
  47. import io.nexilis.service.core.extension
  48. import io.nexilis.service.core.toHumanStandard
  49. import io.nexilis.service.data.entities.Message
  50. import io.nexilis.service.data.viewmodels.ProgressViewModel
  51. import java.io.File
  52. import java.io.FileInputStream
  53. @Composable
  54. fun ContentChat(modifier: Modifier = Modifier, message: Message) {
  55. val context = LocalContext.current
  56. if (message.image_id.isNotEmpty()) {
  57. Column(modifier = Modifier.padding(2.dp)) {
  58. val file = File(context.filesDir, message.image_id)
  59. val uri = FileProvider.getUriForFile(
  60. context, context.applicationContext.packageName + ".provider", file
  61. )
  62. var width: Int
  63. var height: Int
  64. FileInputStream(file).use {
  65. val exif = ExifInterface(it)
  66. width = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 1)
  67. height = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 1)
  68. val orientation = exif.getAttributeInt(
  69. ExifInterface.TAG_ORIENTATION,
  70. ExifInterface.ORIENTATION_UNDEFINED
  71. )
  72. if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270) {
  73. height = exif.getAttributeInt(ExifInterface.TAG_IMAGE_WIDTH, 1)
  74. width = exif.getAttributeInt(ExifInterface.TAG_IMAGE_LENGTH, 1)
  75. }
  76. }
  77. if (width == 0) {
  78. FileInputStream(file).use {
  79. val bitmap = BitmapFactory.decodeStream(it)
  80. width = bitmap.width
  81. if (height == 0) height = bitmap.height
  82. }
  83. }
  84. val aspectRatio = width.toFloat() / height.toFloat()
  85. Image(
  86. painter = rememberAsyncImagePainter(uri),
  87. contentDescription = "",
  88. modifier = modifier
  89. .aspectRatio(aspectRatio)
  90. .graphicsLayer {
  91. shape = RoundedCornerShape(12.dp)
  92. clip = true
  93. },
  94. contentScale = ContentScale.Fit
  95. )
  96. if (message.message_text.isNotEmpty()) {
  97. Text(
  98. text = message.message_text,
  99. modifier = Modifier.padding(8.dp),
  100. color = MaterialTheme.colorScheme.onSurface,
  101. style = MaterialTheme.typography.bodyMedium,
  102. )
  103. }
  104. }
  105. } else if (message.file_id.isNotEmpty()) {
  106. ListItem(
  107. headlineContent = {
  108. Text(
  109. text = message.message_text.split("|").first(),
  110. color = MaterialTheme.colorScheme.onSurface,
  111. style = MaterialTheme.typography.bodyMedium,
  112. maxLines = 2,
  113. overflow = TextOverflow.Ellipsis
  114. )
  115. },
  116. modifier = Modifier.fillMaxWidth(0.76f),
  117. supportingContent = {
  118. FileDesc(file = message.file_id)
  119. },
  120. leadingContent = {
  121. FileType(message = message)
  122. },
  123. trailingContent = {
  124. FileProgress(message = message)
  125. },
  126. colors = ListItemDefaults.colors(
  127. containerColor = Color.Transparent
  128. )
  129. )
  130. } else if (message.audio_id.isNotEmpty()) {
  131. Column(
  132. modifier = Modifier
  133. .padding(2.dp)
  134. .widthIn(max = 210.dp)
  135. ) {
  136. Row(modifier = Modifier
  137. .graphicsLayer {
  138. shape = RoundedCornerShape(12.dp)
  139. clip = true
  140. }
  141. .background(MaterialTheme.colorScheme.surface)) {
  142. Icon(
  143. modifier = Modifier
  144. .size(48.dp)
  145. .padding(start = 8.dp)
  146. .align(Alignment.CenterVertically),
  147. imageVector = Icons.Default.Headset,
  148. contentDescription = null,
  149. tint = Color.Orange
  150. )
  151. Column {
  152. Row(
  153. modifier = Modifier.padding(
  154. start = 4.dp,
  155. top = 8.dp,
  156. end = 8.dp,
  157. bottom = 8.dp
  158. )
  159. ) {
  160. Icon(
  161. modifier = Modifier
  162. .size(32.dp)
  163. .align(Alignment.CenterVertically),
  164. imageVector = Icons.Default.PlayArrow,
  165. contentDescription = null,
  166. tint = MaterialTheme.colorScheme.primary
  167. )
  168. LinearProgressIndicator(
  169. progress = { 0.5f },
  170. modifier = Modifier
  171. .align(Alignment.CenterVertically)
  172. .fillMaxWidth(),
  173. )
  174. }
  175. Text(
  176. modifier = Modifier.padding(
  177. start = 4.dp,
  178. top = 8.dp,
  179. end = 8.dp,
  180. bottom = 8.dp
  181. ),
  182. color = MaterialTheme.colorScheme.onSurface,
  183. text = message.message_text.split("|").first(),
  184. style = MaterialTheme.typography.labelSmall,
  185. maxLines = 1,
  186. overflow = TextOverflow.Ellipsis
  187. )
  188. }
  189. }
  190. val messageText = message.message_text.split("|").last()
  191. if (messageText.isNotEmpty()) {
  192. Text(
  193. text = messageText,
  194. modifier = Modifier.padding(8.dp),
  195. color = MaterialTheme.colorScheme.onSurface,
  196. style = MaterialTheme.typography.bodyMedium,
  197. )
  198. }
  199. }
  200. } else {
  201. Text(
  202. text = message.message_text,
  203. modifier = Modifier.padding(8.dp),
  204. color = MaterialTheme.colorScheme.onSurface,
  205. style = MaterialTheme.typography.bodyMedium,
  206. )
  207. }
  208. }
  209. @Composable
  210. fun FileType(message: Message, modifier: Modifier = Modifier) {
  211. val imageVector = when (message.file_id.extension) {
  212. "apk", "aab" -> ImageVector.vectorResource(id = R.drawable.ic_apk)
  213. "aac", "mp3", "m4a", "ogg" -> ImageVector.vectorResource(id = R.drawable.ic_audio)
  214. "xls", "xlsx" -> ImageVector.vectorResource(id = R.drawable.ic_excel)
  215. "jpg", "jpeg", "webp", "png", "svg" -> ImageVector.vectorResource(id = R.drawable.ic_image)
  216. "pdf" -> ImageVector.vectorResource(id = R.drawable.ic_pdf)
  217. "ppt", "pptx" -> ImageVector.vectorResource(id = R.drawable.ic_ppt)
  218. "txt" -> ImageVector.vectorResource(id = R.drawable.ic_txt)
  219. "mp4", "mov", "3gp" -> ImageVector.vectorResource(id = R.drawable.ic_video)
  220. "doc", "docx" -> ImageVector.vectorResource(id = R.drawable.ic_word)
  221. "zip", "gz", "rar" -> ImageVector.vectorResource(id = R.drawable.ic_zip)
  222. else -> ImageVector.vectorResource(id = R.drawable.ic_file)
  223. }
  224. Image(
  225. modifier = modifier
  226. .size(48.dp),
  227. imageVector = imageVector,
  228. contentDescription = null
  229. )
  230. }
  231. @Composable
  232. fun FileDesc(file: String, modifier: Modifier = Modifier) {
  233. val context = LocalContext.current
  234. val f = File(context.filesDir, file)
  235. if (f.exists()) {
  236. Text(
  237. modifier = modifier,
  238. text = "${
  239. f.length().toHumanStandard()
  240. } • ${file.extension.toUpperCase(Locale.current)}",
  241. color = MaterialTheme.colorScheme.onSurface,
  242. style = MaterialTheme.typography.labelSmall,
  243. )
  244. }
  245. }
  246. @Composable
  247. fun FileProgress(message: Message, modifier: Modifier = Modifier) {
  248. val context = LocalContext.current
  249. val progressViewModel: ProgressViewModel = hiltViewModel()
  250. val progress by progressViewModel.get(message.message_id).observeAsState()
  251. progress?.let {
  252. if (it.value > 0f && it.value < 1f) {
  253. CircularProgressIndicator(
  254. progress = { it.value },
  255. modifier = modifier
  256. .size(32.dp),
  257. )
  258. }
  259. }
  260. val file = File(context.filesDir, message.file_id)
  261. if (!file.exists()) {
  262. IconButton(
  263. onClick = { /*TODO*/ },
  264. modifier = modifier
  265. ) {
  266. Icon(
  267. imageVector = Icons.Default.DownloadForOffline,
  268. contentDescription = null,
  269. tint = Color.LightGray
  270. )
  271. }
  272. }
  273. }