Chat.kt 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. package io.nexilis.alpha.ui.main
  2. import android.net.Uri
  3. import androidx.compose.foundation.background
  4. import androidx.compose.foundation.layout.*
  5. import androidx.compose.foundation.lazy.LazyColumn
  6. import androidx.compose.foundation.lazy.rememberLazyListState
  7. import androidx.compose.foundation.lazy.items
  8. import androidx.compose.foundation.shape.CircleShape
  9. import androidx.compose.foundation.shape.RoundedCornerShape
  10. import androidx.compose.foundation.text.KeyboardOptions
  11. import androidx.compose.material.icons.Icons
  12. import androidx.compose.material.icons.automirrored.filled.Send
  13. import androidx.compose.material3.*
  14. import androidx.compose.runtime.*
  15. import androidx.compose.runtime.livedata.observeAsState
  16. import androidx.compose.runtime.saveable.rememberSaveable
  17. import androidx.compose.ui.Modifier
  18. import androidx.compose.ui.graphics.Color
  19. import androidx.compose.ui.graphics.graphicsLayer
  20. import androidx.compose.ui.platform.LocalContext
  21. import androidx.compose.ui.text.TextRange
  22. import androidx.compose.ui.text.input.KeyboardCapitalization
  23. import androidx.compose.ui.text.input.TextFieldValue
  24. import androidx.compose.ui.unit.dp
  25. import androidx.hilt.navigation.compose.hiltViewModel
  26. import androidx.navigation.NavHostController
  27. import io.nexilis.alpha.ui.components.AttachmentItemList
  28. import io.nexilis.alpha.ui.components.Attachments
  29. import io.nexilis.alpha.ui.components.ContentChat
  30. import io.nexilis.alpha.ui.components.LeftBubbleChat
  31. import io.nexilis.alpha.ui.components.RightBubbleChat
  32. import io.nexilis.alpha.ui.components.getAttachmentItem
  33. import io.nexilis.alpha.ui.screen.Screen
  34. import io.nexilis.service.data.entities.Buddy
  35. import io.nexilis.service.data.entities.Message
  36. import io.nexilis.service.data.viewmodels.BuddyViewModel
  37. import io.nexilis.service.data.viewmodels.MessageViewModel
  38. import kotlinx.serialization.encodeToString
  39. import kotlinx.serialization.json.Json
  40. @OptIn(ExperimentalLayoutApi::class)
  41. @Composable
  42. fun Chat(
  43. navController: NavHostController,
  44. contentPadding: PaddingValues,
  45. pin: String,
  46. me: Buddy
  47. ) {
  48. var textInput by rememberSaveable(stateSaver = TextFieldValue.Saver) {
  49. mutableStateOf(TextFieldValue("", TextRange(0, 7)))
  50. }
  51. val messageModel: MessageViewModel = hiltViewModel()
  52. val list by messageModel.getOpposite(pin).observeAsState()
  53. val state = rememberLazyListState()
  54. val context = LocalContext.current
  55. var openAlertDialog by remember { mutableStateOf(false) }
  56. val attachments = remember { mutableStateListOf<Uri>() }
  57. Column(
  58. modifier = Modifier
  59. .fillMaxSize()
  60. .consumeWindowInsets(contentPadding)
  61. .imePadding()
  62. .imeNestedScroll()
  63. ) {
  64. LazyColumn(
  65. modifier = Modifier
  66. .weight(1f),
  67. state = state,
  68. reverseLayout = true
  69. ) {
  70. list?.let { l ->
  71. items(l) { message ->
  72. ListItem(modifier = Modifier.fillMaxWidth(), headlineContent = {
  73. Row(
  74. modifier = Modifier.fillMaxWidth(),
  75. horizontalArrangement = if (message.f_pin == me.f_pin) Arrangement.End else Arrangement.Start
  76. ) {
  77. if (message.f_pin == me.f_pin) {
  78. Spacer(modifier = Modifier.weight(0.1f))
  79. RightBubbleChat(
  80. modifier = Modifier.weight(weight = 0.9f, fill = false),
  81. message = message,
  82. ) {
  83. ContentChat(
  84. modifier = Modifier.fillMaxWidth(0.7f),
  85. message = message
  86. )
  87. }
  88. } else {
  89. LeftBubbleChat(
  90. modifier = Modifier.weight(weight = 0.9f, fill = false),
  91. message = message
  92. ) {
  93. ContentChat(
  94. modifier = Modifier.fillMaxWidth(0.7f),
  95. message = message
  96. )
  97. }
  98. Spacer(modifier = Modifier.weight(0.1f))
  99. }
  100. }
  101. }, colors = ListItemDefaults.colors(containerColor = Color.Transparent))
  102. }
  103. }
  104. }
  105. TextField(
  106. modifier = Modifier
  107. .fillMaxWidth()
  108. .graphicsLayer {
  109. shape = CircleShape
  110. clip = true
  111. }
  112. .padding(4.dp),
  113. value = textInput,
  114. onValueChange = { textInput = it },
  115. label = null,
  116. placeholder = {
  117. Text(text = "Typing here...", color = MaterialTheme.colorScheme.onSurface)
  118. },
  119. leadingIcon = {
  120. Attachments(
  121. modifier = Modifier
  122. .height(300.dp)
  123. .fillMaxWidth()
  124. .padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
  125. .graphicsLayer {
  126. shape = RoundedCornerShape(16.dp)
  127. clip = true
  128. }
  129. .background(MaterialTheme.colorScheme.surfaceContainer),
  130. onAttachment = { list ->
  131. attachments.addAll(list)
  132. val items = list.map {
  133. context.getAttachmentItem(it)
  134. }
  135. if (items.size == 1) {
  136. val params = AttachmentItemList(items)
  137. val data = Uri.encode(Json.encodeToString(params))
  138. navController.navigate(Screen.AttachmentCaption.route + "?data=${data}&pin=${pin}") {
  139. launchSingleTop = true
  140. restoreState = true
  141. }
  142. } else if (items.size > 1) {
  143. openAlertDialog = true
  144. }
  145. }
  146. )
  147. },
  148. trailingIcon = {
  149. Row {
  150. IconButton(onClick = {
  151. if (pin.trim().isEmpty()) {
  152. return@IconButton
  153. }
  154. if (textInput.text.trim().isEmpty()) {
  155. return@IconButton
  156. }
  157. me.let {
  158. messageModel.send(
  159. context,
  160. Message(
  161. message_id = System.nanoTime().toString(),
  162. f_pin = it.f_pin,
  163. l_pin = pin,
  164. message_scope_id = "3", // 3: chat, 15: sms, 16: email
  165. server_date = System.currentTimeMillis(),
  166. status = "1",
  167. message_text = textInput.text,
  168. opposite_pin = pin,
  169. f_display_name = "${it.first_name} ${it.last_name}".trim(),
  170. message_large_text = "",
  171. message_text_plain = "",
  172. uri = if (attachments.size > 0) attachments[0] else Uri.EMPTY
  173. )
  174. )
  175. textInput = TextFieldValue("")
  176. }
  177. }) {
  178. Icon(
  179. imageVector = Icons.AutoMirrored.Filled.Send,
  180. contentDescription = "",
  181. tint = MaterialTheme.colorScheme.primary
  182. )
  183. }
  184. }
  185. },
  186. colors = TextFieldDefaults.colors(
  187. focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer,
  188. unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer,
  189. disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
  190. focusedIndicatorColor = Color.Transparent,
  191. unfocusedIndicatorColor = Color.Transparent,
  192. disabledIndicatorColor = Color.Transparent,
  193. errorIndicatorColor = Color.Transparent,
  194. ),
  195. keyboardOptions = KeyboardOptions(KeyboardCapitalization.Sentences),
  196. maxLines = 3
  197. )
  198. if (openAlertDialog) {
  199. val buddyModel: BuddyViewModel = hiltViewModel()
  200. val buddy by buddyModel.getBuddy(pin).observeAsState()
  201. buddy?.let {
  202. AlertDialog(
  203. text = {
  204. Text(text = "Send ${attachments.size} documents to ${it.first_name} ${it.last_name}?".trim())
  205. },
  206. onDismissRequest = {
  207. openAlertDialog = false
  208. },
  209. confirmButton = {
  210. TextButton(
  211. onClick = {
  212. openAlertDialog = false
  213. me.let {
  214. attachments.forEach { uri ->
  215. messageModel.send(
  216. context,
  217. Message(
  218. message_id = System.nanoTime().toString(),
  219. f_pin = it.f_pin,
  220. l_pin = pin,
  221. message_scope_id = "3", // 3: chat, 15: sms, 16: email
  222. server_date = System.currentTimeMillis(),
  223. status = "1",
  224. message_text = "",
  225. opposite_pin = pin,
  226. f_display_name = "${it.first_name} ${it.last_name}".trim(),
  227. message_large_text = "",
  228. message_text_plain = "",
  229. uri = uri
  230. )
  231. )
  232. }
  233. textInput = TextFieldValue("")
  234. }
  235. }
  236. ) {
  237. Text("Send")
  238. }
  239. },
  240. dismissButton = {
  241. TextButton(
  242. onClick = {
  243. openAlertDialog = false
  244. }
  245. ) {
  246. Text("Cancel")
  247. }
  248. }
  249. )
  250. }
  251. }
  252. }
  253. }