yayan преди 1 година
родител
ревизия
de15fb4c72

+ 2 - 4
app/src/main/AndroidManifest.xml

@@ -4,15 +4,13 @@
 
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
     <uses-permission
         android:name="android.permission.READ_EXTERNAL_STORAGE"
         android:maxSdkVersion="32" />
-    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
-    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
-    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
-
 
     <uses-feature android:name="android.hardware.camera.any" />
+    <uses-feature android:name="android.hardware.microphone" />
 
     <application
         android:name=".AlphaApplication"

+ 115 - 110
app/src/main/java/io/nexilis/alpha/ui/components/Attachments.kt

@@ -11,38 +11,39 @@ import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.consumeWindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.imeNestedScroll
+import androidx.compose.foundation.layout.imePadding
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.Send
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.AttachFile
 import androidx.compose.material.icons.filled.Camera
 import androidx.compose.material.icons.filled.Headset
 import androidx.compose.material.icons.filled.Image
 import androidx.compose.material.icons.filled.LocationOn
-import androidx.compose.material.icons.filled.Person
+import androidx.compose.material.icons.filled.PhotoCamera
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
-import androidx.compose.material3.TextField
-import androidx.compose.material3.TextFieldDefaults
 import androidx.compose.material3.TooltipDefaults
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -52,14 +53,10 @@ import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalSoftwareKeyboardController
 import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.KeyboardCapitalization
-import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Popup
 import androidx.core.content.FileProvider
 import androidx.core.net.toUri
-import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.navigation.NavHostController
 import androidx.navigation.NavType
 import coil.compose.AsyncImage
@@ -72,9 +69,7 @@ import io.nexilis.service.core.Orange
 import io.nexilis.service.core.Purple
 import io.nexilis.service.core.createFile
 import io.nexilis.service.core.getMimeType
-import io.nexilis.service.core.getSharedPreferences
-import io.nexilis.service.data.entities.Message
-import io.nexilis.service.data.viewmodels.MessageViewModel
+import io.nexilis.service.data.entities.Buddy
 import kotlinx.parcelize.Parcelize
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.Serializable
@@ -152,6 +147,32 @@ fun Attachments(modifier: Modifier, onAttachment: (List<Uri>) -> Unit) {
                                 .show()
                         }
                     }
+                    val launcherCaptureVideo =
+                        rememberLauncherForActivityResult(ActivityResultContracts.CaptureVideo()) {
+                            if (it) {
+                                onAttachment(listOf(tempUri))
+                            }
+                            isOpenMenu = false
+                        }
+                    val permissionLauncherCaptureVideo = rememberLauncherForActivityResult(
+                        ActivityResultContracts.RequestMultiplePermissions()
+                    ) {
+                        var accept = true
+                        it.values.forEach { r ->
+                            if (!r) accept = false
+                        }
+                        if (accept) {
+                            val file = context.createFile(".mp4")
+                            tempUri = FileProvider.getUriForFile(
+                                context,
+                                BuildConfig.APPLICATION_ID + ".provider", file
+                            )
+                            launcherCaptureVideo.launch(tempUri)
+                        } else {
+                            Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT)
+                                .show()
+                        }
+                    }
                     Column {
                         AttachmentButton(
                             color = MaterialTheme.colorScheme.primary,
@@ -168,16 +189,17 @@ fun Attachments(modifier: Modifier, onAttachment: (List<Uri>) -> Unit) {
                             )
                         }
                         Spacer(modifier = Modifier.size(32.dp))
-                        AttachmentButton(color = Color.Orange, onClick = {
+                        AttachmentButton(color = Color.Purple, onClick = {
                             mimeType = arrayOf(
-                                "audio/*"
+                                "image/*",
+                                "video/*"
                             )
                             permissionLauncherFiles.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
                         }, text = {
-                            Text(text = "Audio", style = MaterialTheme.typography.bodySmall)
+                            Text(text = "Gallery", style = MaterialTheme.typography.bodySmall)
                         }) {
                             Icon(
-                                imageVector = Icons.Default.Headset,
+                                imageVector = Icons.Default.Image,
                                 contentDescription = "",
                                 tint = Color.White
                             )
@@ -188,20 +210,25 @@ fun Attachments(modifier: Modifier, onAttachment: (List<Uri>) -> Unit) {
                         AttachmentButton(color = Color.Red, onClick = {
                             permissionLauncherCamera.launch(Manifest.permission.CAMERA)
                         }, text = {
-                            Text(text = "Camera", style = MaterialTheme.typography.bodySmall)
+                            Text(text = "Photo", style = MaterialTheme.typography.bodySmall)
                         }) {
                             Icon(
-                                imageVector = Icons.Default.Camera,
+                                imageVector = Icons.Default.PhotoCamera,
                                 contentDescription = "",
                                 tint = Color.White
                             )
                         }
                         Spacer(modifier = Modifier.size(32.dp))
-                        AttachmentButton(color = Color.DarkLimeGreen, onClick = {}, text = {
-                            Text(text = "Location", style = MaterialTheme.typography.bodySmall)
+                        AttachmentButton(color = Color.Orange, onClick = {
+                            mimeType = arrayOf(
+                                "audio/*"
+                            )
+                            permissionLauncherFiles.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
+                        }, text = {
+                            Text(text = "Audio", style = MaterialTheme.typography.bodySmall)
                         }) {
                             Icon(
-                                imageVector = Icons.Default.LocationOn,
+                                imageVector = Icons.Default.Headset,
                                 contentDescription = "",
                                 tint = Color.White
                             )
@@ -209,27 +236,28 @@ fun Attachments(modifier: Modifier, onAttachment: (List<Uri>) -> Unit) {
                     }
                     Spacer(modifier = Modifier.size(16.dp))
                     Column {
-                        AttachmentButton(color = Color.Purple, onClick = {
-                            mimeType = arrayOf(
-                                "image/*",
-                                "video/*"
+                        AttachmentButton(color = Color.DarkLimeGreen, onClick = {
+                            permissionLauncherCaptureVideo.launch(
+                                arrayOf(
+                                    Manifest.permission.CAMERA,
+                                    Manifest.permission.RECORD_AUDIO
+                                )
                             )
-                            permissionLauncherFiles.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
                         }, text = {
-                            Text(text = "Gallery", style = MaterialTheme.typography.bodySmall)
+                            Text(text = "Video", style = MaterialTheme.typography.bodySmall)
                         }) {
                             Icon(
-                                imageVector = Icons.Default.Image,
+                                imageVector = Icons.Default.Camera,
                                 contentDescription = "",
                                 tint = Color.White
                             )
                         }
                         Spacer(modifier = Modifier.size(32.dp))
                         AttachmentButton(color = Color.BlueSky, onClick = {}, text = {
-                            Text(text = "Contact", style = MaterialTheme.typography.bodySmall)
+                            Text(text = "Location", style = MaterialTheme.typography.bodySmall)
                         }) {
                             Icon(
-                                imageVector = Icons.Default.Person,
+                                imageVector = Icons.Default.LocationOn,
                                 contentDescription = "",
                                 tint = Color.White
                             )
@@ -329,94 +357,71 @@ val AttachmentItemListType = object : NavType<AttachmentItemList>(isNullableAllo
     }
 }
 
+@OptIn(ExperimentalLayoutApi::class)
 @Composable
 fun AttachmentCaption(
     navController: NavHostController,
+    contentPadding: PaddingValues,
     pin: String,
+    me: Buddy,
     attachments: AttachmentItemList
 ) {
-    var textInput by rememberSaveable(stateSaver = TextFieldValue.Saver) {
-        mutableStateOf(TextFieldValue("", TextRange(0, 7)))
-    }
-    val messageModel: MessageViewModel = hiltViewModel()
-    val context = LocalContext.current
-    Column(modifier = Modifier.fillMaxSize()) {
-        attachments.list[0]?.let {
-            if (it.mimeType.startsWith("image")) {
-                AsyncImage(
-                    model = ImageRequest.Builder(LocalContext.current)
-                        .data(it.uri)
-                        .addHeader("Cookie", "PHPSESSID=123;MOBILE=123")
-                        .crossfade(true)
-                        .build(),
-                    placeholder = painterResource(R.drawable.ic_placeholder),
-                    contentDescription = "",
-                    contentScale = ContentScale.Fit,
-                    modifier = Modifier
-                        .weight(1f)
-                )
-            }
-        }
-        TextField(
-            modifier = Modifier
-                .fillMaxWidth()
-                .graphicsLayer {
-                    shape = CircleShape
-                    clip = true
-                },
-            value = textInput,
-            onValueChange = { textInput = it },
-            label = null,
-            placeholder = {
-                Text(text = "Add a caption...", color = MaterialTheme.colorScheme.onSurface)
-            },
-            trailingIcon = {
-                Row {
-                    IconButton(onClick = {
-                        val me = context.getSharedPreferences().getString("pin", null)
-                        me?.let {
-                            attachments.list[0]?.let { item ->
-                                messageModel.send(
-                                    context,
-                                    Message(
-                                        message_id = System.nanoTime().toString(),
-                                        f_pin = it,
-                                        l_pin = pin,
-                                        message_scope_id = "3", // 3: chat, 15: sms, 16: email
-                                        server_date = System.currentTimeMillis(),
-                                        status = "1",
-                                        message_text = textInput.text,
-                                        opposite_pin = pin,
-                                        f_display_name = "",
-                                        message_large_text = "",
-                                        message_text_plain = "",
-                                        uri = item.uri
-                                    )
-                                )
-                            }
-                        }
-                        textInput = TextFieldValue("")
-                        navController.popBackStack()
-                    }) {
-                        Icon(
-                            imageVector = Icons.AutoMirrored.Filled.Send,
+    Column(
+        modifier = Modifier
+            .fillMaxSize()
+            .consumeWindowInsets(contentPadding)
+            .imePadding()
+            .imeNestedScroll()
+    ) {
+        LazyColumn(
+            modifier = Modifier.weight(1.0f),
+            reverseLayout = true,
+            verticalArrangement = Arrangement.Top
+        ) {
+            attachments.list[0]?.let {
+                item {
+                    if (it.mimeType.startsWith("image")) {
+                        AsyncImage(
+                            model = ImageRequest.Builder(LocalContext.current)
+                                .data(it.uri)
+                                .addHeader("Cookie", "PHPSESSID=123;MOBILE=123")
+                                .crossfade(true)
+                                .build(),
+                            placeholder = painterResource(R.drawable.ic_placeholder),
                             contentDescription = "",
-                            tint = MaterialTheme.colorScheme.primary
+                            contentScale = ContentScale.Fit,
+                            modifier = Modifier.fillMaxSize()
                         )
+                    } else if (it.mimeType.startsWith("video")) {
+
+                    } else if (it.mimeType.startsWith("audio")) {
+                        Icon(
+                            imageVector = Icons.Default.Headset,
+                            contentDescription = null,
+                            modifier = Modifier.fillMaxSize(),
+                            tint = Color.Orange
+                        )
+                    } else {
+
                     }
                 }
-            },
-            colors = TextFieldDefaults.colors(
-                focusedContainerColor = MaterialTheme.colorScheme.surface,
-                unfocusedContainerColor = MaterialTheme.colorScheme.surface,
-                disabledContainerColor = MaterialTheme.colorScheme.surface,
-                focusedIndicatorColor = Color.Transparent,
-                unfocusedIndicatorColor = Color.Transparent,
-                disabledIndicatorColor = Color.Transparent,
-                errorIndicatorColor = Color.Transparent,
-            ),
-            keyboardOptions = KeyboardOptions(KeyboardCapitalization.Sentences),
-            maxLines = 3
+            }
+        }
+        val list = arrayListOf<Uri>()
+        attachments.list.forEach {
+            it?.let {
+                list.add(it.uri)
+            }
+        }
+        InputChat(
+            navController = navController,
+            pin = pin,
+            me = me,
+            placeHolderText = "Add a caption...",
+            enableAttachment = false,
+            enableEmpty = true,
+            popAfterSent = true,
+            attachments = list
         )
     }
 }

+ 218 - 0
app/src/main/java/io/nexilis/alpha/ui/components/InputChat.kt

@@ -0,0 +1,218 @@
+package io.nexilis.alpha.ui.components
+
+import android.net.Uri
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.Send
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import io.nexilis.alpha.ui.screen.Screen
+import io.nexilis.service.data.entities.Buddy
+import io.nexilis.service.data.entities.Message
+import io.nexilis.service.data.viewmodels.BuddyViewModel
+import io.nexilis.service.data.viewmodels.MessageViewModel
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+
+@Composable
+fun InputChat(
+    navController: NavHostController,
+    pin: String,
+    me: Buddy,
+    placeHolderText: String = "Typing here...",
+    enableAttachment: Boolean = true,
+    enableEmpty: Boolean = false,
+    popAfterSent: Boolean = false,
+    attachments: ArrayList<Uri> = arrayListOf()
+) {
+    val context = LocalContext.current
+    val messageModel: MessageViewModel = hiltViewModel()
+    var textInput by rememberSaveable(stateSaver = TextFieldValue.Saver) {
+        mutableStateOf(TextFieldValue("", TextRange(0, 7)))
+    }
+    var openAlertDialog by remember { mutableStateOf(false) }
+    val attachmentState = remember { mutableStateListOf<Uri>() }
+    attachmentState.addAll(attachments)
+    val leadingIcon: (@Composable () -> Unit)? = if (enableAttachment) {
+        {
+            Attachments(
+                modifier = Modifier
+                    .height(300.dp)
+                    .fillMaxWidth()
+                    .padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
+                    .graphicsLayer {
+                        shape = RoundedCornerShape(16.dp)
+                        clip = true
+                    }
+                    .background(MaterialTheme.colorScheme.surfaceContainer),
+                onAttachment = { list ->
+                    attachmentState.addAll(list)
+                    val items = list.map {
+                        context.getAttachmentItem(it)
+                    }
+                    if (items.size == 1) {
+                        val params = AttachmentItemList(items)
+                        val data = Uri.encode(Json.encodeToString(params))
+                        navController.navigate(Screen.AttachmentCaption.route + "?data=${data}&pin=${pin}") {
+                            launchSingleTop = true
+                            restoreState = true
+                        }
+                    } else if (items.size > 1) {
+                        openAlertDialog = true
+                    }
+                }
+            )
+        }
+    } else null
+    TextField(
+        modifier = Modifier
+            .fillMaxWidth()
+            .graphicsLayer {
+                shape = CircleShape
+                clip = true
+            }
+            .padding(4.dp),
+        value = textInput,
+        onValueChange = { textInput = it },
+        label = null,
+        placeholder = {
+            Text(text = placeHolderText, color = MaterialTheme.colorScheme.onSurface)
+        },
+        leadingIcon = leadingIcon,
+        trailingIcon = {
+            Row {
+                IconButton(onClick = {
+                    if (pin.trim().isEmpty()) {
+                        return@IconButton
+                    }
+                    if (!enableEmpty && textInput.text.trim().isEmpty()) {
+                        return@IconButton
+                    }
+                    me.let {
+                        messageModel.send(
+                            context,
+                            Message(
+                                message_id = System.nanoTime().toString(),
+                                f_pin = it.f_pin,
+                                l_pin = pin,
+                                message_scope_id = "3", // 3: chat, 15: sms, 16: email
+                                server_date = System.currentTimeMillis(),
+                                status = "1",
+                                message_text = textInput.text,
+                                opposite_pin = pin,
+                                f_display_name = "${it.first_name} ${it.last_name}".trim(),
+                                message_large_text = "",
+                                message_text_plain = "",
+                                uri = if (attachmentState.size > 0) attachmentState[0] else Uri.EMPTY
+                            )
+                        )
+                        textInput = TextFieldValue("")
+                        if (popAfterSent) navController.popBackStack()
+                    }
+                }) {
+                    Icon(
+                        imageVector = Icons.AutoMirrored.Filled.Send,
+                        contentDescription = "",
+                        tint = MaterialTheme.colorScheme.primary
+                    )
+                }
+            }
+        },
+        colors = TextFieldDefaults.colors(
+            focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer,
+            unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer,
+            disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
+            focusedIndicatorColor = Color.Transparent,
+            unfocusedIndicatorColor = Color.Transparent,
+            disabledIndicatorColor = Color.Transparent,
+            errorIndicatorColor = Color.Transparent,
+        ),
+        keyboardOptions = KeyboardOptions(KeyboardCapitalization.Sentences),
+        maxLines = 3
+    )
+    if (openAlertDialog) {
+        val buddyModel: BuddyViewModel = hiltViewModel()
+        val buddy by buddyModel.getBuddy(pin).observeAsState()
+        buddy?.let {
+            AlertDialog(
+                text = {
+                    Text(text = "Send ${attachmentState.size} documents to ${it.first_name} ${it.last_name}?".trim())
+                },
+                onDismissRequest = {
+                    openAlertDialog = false
+                },
+                confirmButton = {
+                    TextButton(
+                        onClick = {
+                            openAlertDialog = false
+                            me.let {
+                                attachmentState.forEach { uri ->
+                                    messageModel.send(
+                                        context,
+                                        Message(
+                                            message_id = System.nanoTime().toString(),
+                                            f_pin = it.f_pin,
+                                            l_pin = pin,
+                                            message_scope_id = "3", // 3: chat, 15: sms, 16: email
+                                            server_date = System.currentTimeMillis(),
+                                            status = "1",
+                                            message_text = "",
+                                            opposite_pin = pin,
+                                            f_display_name = "${it.first_name} ${it.last_name}".trim(),
+                                            message_large_text = "",
+                                            message_text_plain = "",
+                                            uri = uri
+                                        )
+                                    )
+                                }
+                                textInput = TextFieldValue("")
+                            }
+                        }
+                    ) {
+                        Text("Send")
+                    }
+                },
+                dismissButton = {
+                    TextButton(
+                        onClick = {
+                            openAlertDialog = false
+                        }
+                    ) {
+                        Text("Cancel")
+                    }
+                }
+            )
+        }
+    }
+}

+ 2 - 2
app/src/main/java/io/nexilis/alpha/ui/components/Title.kt

@@ -25,13 +25,13 @@ fun Title(navController: NavHostController, navMainController: NavHostController
     var title = ""
     navBackStackEntry?.destination?.route?.let { route ->
         try {
-            title = screenItems.first { it.route == route }.title
+            title = screenItems.first { route.startsWith(it.route) }.title
         } catch (ignored: Exception) {
         }
     }
     navMainBackStackEntry?.destination?.route?.let { route ->
         try {
-            title = screenItems.first { it.route == route }.title
+            title = screenItems.first { route.startsWith(it.route) }.title
         } catch (ignored: Exception) {
         }
     }

+ 2 - 175
app/src/main/java/io/nexilis/alpha/ui/main/Chat.kt

@@ -1,43 +1,22 @@
 package io.nexilis.alpha.ui.main
 
-import android.net.Uri
-import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.automirrored.filled.Send
 import androidx.compose.material3.*
 import androidx.compose.runtime.*
 import androidx.compose.runtime.livedata.observeAsState
-import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.graphicsLayer
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.KeyboardCapitalization
-import androidx.compose.ui.text.input.TextFieldValue
-import androidx.compose.ui.unit.dp
 import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.navigation.NavHostController
-import io.nexilis.alpha.ui.components.AttachmentItemList
-import io.nexilis.alpha.ui.components.Attachments
 import io.nexilis.alpha.ui.components.ContentChat
+import io.nexilis.alpha.ui.components.InputChat
 import io.nexilis.alpha.ui.components.LeftBubbleChat
 import io.nexilis.alpha.ui.components.RightBubbleChat
-import io.nexilis.alpha.ui.components.getAttachmentItem
-import io.nexilis.alpha.ui.screen.Screen
 import io.nexilis.service.data.entities.Buddy
-import io.nexilis.service.data.entities.Message
-import io.nexilis.service.data.viewmodels.BuddyViewModel
 import io.nexilis.service.data.viewmodels.MessageViewModel
-import kotlinx.serialization.encodeToString
-import kotlinx.serialization.json.Json
 
 @OptIn(ExperimentalLayoutApi::class)
 @Composable
@@ -47,15 +26,9 @@ fun Chat(
     pin: String,
     me: Buddy
 ) {
-    var textInput by rememberSaveable(stateSaver = TextFieldValue.Saver) {
-        mutableStateOf(TextFieldValue("", TextRange(0, 7)))
-    }
     val messageModel: MessageViewModel = hiltViewModel()
     val list by messageModel.getOpposite(pin).observeAsState()
     val state = rememberLazyListState()
-    val context = LocalContext.current
-    var openAlertDialog by remember { mutableStateOf(false) }
-    val attachments = remember { mutableStateListOf<Uri>() }
     Column(
         modifier = Modifier
             .fillMaxSize()
@@ -104,153 +77,7 @@ fun Chat(
                 }
             }
         }
-        TextField(
-            modifier = Modifier
-                .fillMaxWidth()
-                .graphicsLayer {
-                    shape = CircleShape
-                    clip = true
-                }
-                .padding(4.dp),
-            value = textInput,
-            onValueChange = { textInput = it },
-            label = null,
-            placeholder = {
-                Text(text = "Typing here...", color = MaterialTheme.colorScheme.onSurface)
-            },
-            leadingIcon = {
-                Attachments(
-                    modifier = Modifier
-                        .height(300.dp)
-                        .fillMaxWidth()
-                        .padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
-                        .graphicsLayer {
-                            shape = RoundedCornerShape(16.dp)
-                            clip = true
-                        }
-                        .background(MaterialTheme.colorScheme.surfaceContainer),
-                    onAttachment = { list ->
-                        attachments.addAll(list)
-                        val items = list.map {
-                            context.getAttachmentItem(it)
-                        }
-                        if (items.size == 1) {
-                            val params = AttachmentItemList(items)
-                            val data = Uri.encode(Json.encodeToString(params))
-                            navController.navigate(Screen.AttachmentCaption.route + "?data=${data}&pin=${pin}") {
-                                launchSingleTop = true
-                                restoreState = true
-                            }
-                        } else if (items.size > 1) {
-                            openAlertDialog = true
-                        }
-                    }
-                )
-            },
-            trailingIcon = {
-                Row {
-                    IconButton(onClick = {
-                        if (pin.trim().isEmpty()) {
-                            return@IconButton
-                        }
-                        if (textInput.text.trim().isEmpty()) {
-                            return@IconButton
-                        }
-                        me.let {
-                            messageModel.send(
-                                context,
-                                Message(
-                                    message_id = System.nanoTime().toString(),
-                                    f_pin = it.f_pin,
-                                    l_pin = pin,
-                                    message_scope_id = "3", // 3: chat, 15: sms, 16: email
-                                    server_date = System.currentTimeMillis(),
-                                    status = "1",
-                                    message_text = textInput.text,
-                                    opposite_pin = pin,
-                                    f_display_name = "${it.first_name} ${it.last_name}".trim(),
-                                    message_large_text = "",
-                                    message_text_plain = "",
-                                    uri = if (attachments.size > 0) attachments[0] else Uri.EMPTY
-                                )
-                            )
-                            textInput = TextFieldValue("")
-                        }
-                    }) {
-                        Icon(
-                            imageVector = Icons.AutoMirrored.Filled.Send,
-                            contentDescription = "",
-                            tint = MaterialTheme.colorScheme.primary
-                        )
-                    }
-                }
-            },
-            colors = TextFieldDefaults.colors(
-                focusedContainerColor = MaterialTheme.colorScheme.surfaceContainer,
-                unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainer,
-                disabledContainerColor = MaterialTheme.colorScheme.surfaceContainer,
-                focusedIndicatorColor = Color.Transparent,
-                unfocusedIndicatorColor = Color.Transparent,
-                disabledIndicatorColor = Color.Transparent,
-                errorIndicatorColor = Color.Transparent,
-            ),
-            keyboardOptions = KeyboardOptions(KeyboardCapitalization.Sentences),
-            maxLines = 3
-        )
-        if (openAlertDialog) {
-            val buddyModel: BuddyViewModel = hiltViewModel()
-            val buddy by buddyModel.getBuddy(pin).observeAsState()
-            buddy?.let {
-                AlertDialog(
-                    text = {
-                        Text(text = "Send ${attachments.size} documents to ${it.first_name} ${it.last_name}?".trim())
-                    },
-                    onDismissRequest = {
-                        openAlertDialog = false
-                    },
-                    confirmButton = {
-                        TextButton(
-                            onClick = {
-                                openAlertDialog = false
-                                me.let {
-                                    attachments.forEach { uri ->
-                                        messageModel.send(
-                                            context,
-                                            Message(
-                                                message_id = System.nanoTime().toString(),
-                                                f_pin = it.f_pin,
-                                                l_pin = pin,
-                                                message_scope_id = "3", // 3: chat, 15: sms, 16: email
-                                                server_date = System.currentTimeMillis(),
-                                                status = "1",
-                                                message_text = "",
-                                                opposite_pin = pin,
-                                                f_display_name = "${it.first_name} ${it.last_name}".trim(),
-                                                message_large_text = "",
-                                                message_text_plain = "",
-                                                uri = uri
-                                            )
-                                        )
-                                    }
-                                    textInput = TextFieldValue("")
-                                }
-                            }
-                        ) {
-                            Text("Send")
-                        }
-                    },
-                    dismissButton = {
-                        TextButton(
-                            onClick = {
-                                openAlertDialog = false
-                            }
-                        ) {
-                            Text("Cancel")
-                        }
-                    }
-                )
-            }
-        }
+        InputChat(navController = navController, pin = pin, me = me)
     }
 }
 

+ 2 - 0
app/src/main/java/io/nexilis/alpha/ui/main/Main.kt

@@ -128,7 +128,9 @@ fun Main(navController: NavHostController, me: Buddy) {
                             ?.let { data ->
                                 AttachmentCaption(
                                     navController = navMainController,
+                                    contentPadding = contentPadding,
                                     pin = pin,
+                                    me = me,
                                     attachments = data
                                 )
                             }

+ 1 - 1
cpaas-lite/src/main/java/io/nexilis/service/data/daos/MessageStatusDao.kt

@@ -22,7 +22,7 @@ interface MessageStatusDao {
     @Insert(onConflict = OnConflictStrategy.REPLACE)
     suspend fun insert(entity: MessageStatus)
 
-    @Query("update MessageStatus set status = :status, last_update = :lastUpdate where message_id = :messageId and f_pin = :pin")
+    @Query("update MessageStatus set status = :status, last_update = :lastUpdate where message_id = :messageId and f_pin = :pin and status < :status")
     suspend fun updateStatus(
         status: Int,
         lastUpdate: Long = System.currentTimeMillis(),

+ 3 - 0
cpaas-lite/src/main/java/io/nexilis/service/data/viewmodels/MessageViewModel.kt

@@ -52,10 +52,13 @@ class MessageViewModel @Inject constructor(
                     entity.image_id = entity.thumb_id
                 } else if (mimeType.startsWith("audio")) {
                     entity.audio_id = entity.uri.getFileName(context) ?: ""
+                    entity.message_text = entity.audio_id + "|" + entity.message_text
                 } else if (mimeType.startsWith("video")) {
                     entity.video_id = entity.uri.getFileName(context) ?: ""
+                    entity.message_text = entity.video_id + "|" + entity.message_text
                 } else {
                     entity.file_id = entity.uri.getFileName(context) ?: ""
+                    entity.message_text = entity.file_id + "|" + entity.message_text
                 }
             }
             repository.send(context, entity) { id, pin, status ->