yayan hai 1 ano
pai
achega
aed43da087

+ 45 - 3
app/src/main/java/io/nexilis/alpha/ui/components/Attachments.kt

@@ -58,6 +58,7 @@ import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Popup
 import androidx.core.net.toUri
+import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.navigation.NavHostController
 import androidx.navigation.NavType
 import coil.compose.AsyncImage
@@ -67,6 +68,10 @@ import io.nexilis.service.core.BlueSky
 import io.nexilis.service.core.DarkLimeGreen
 import io.nexilis.service.core.Orange
 import io.nexilis.service.core.Purple
+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 kotlinx.parcelize.Parcelize
 import kotlinx.serialization.KSerializer
 import kotlinx.serialization.Serializable
@@ -238,6 +243,7 @@ fun Context.getAttachmentItem(uri: Uri): AttachmentItem? {
             AttachmentItem(
                 cursor.getString(nameIndex),
                 cursor.getLong(sizeIndex),
+                getMimeType(uri),
                 uri
             )
         }
@@ -263,6 +269,7 @@ object UriSerializable : KSerializer<Uri> {
 data class AttachmentItem(
     val name: String,
     val size: Long,
+    val mimeType: String,
     @Serializable(with = UriSerializable::class)
     val uri: Uri
 ) : Parcelable
@@ -288,15 +295,25 @@ val AttachmentItemListType = object : NavType<AttachmentItemList>(isNullableAllo
 }
 
 @Composable
-fun AttachmentCaption(navController: NavHostController, attachments: AttachmentItemList) {
+fun AttachmentCaption(
+    navController: NavHostController,
+    pin: String,
+    attachments: AttachmentItemList
+) {
+    val al = arrayListOf<Uri>()
+    attachments.list.forEach {
+        it?.let {
+            al.add(it.uri)
+        }
+    }
     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 {
-            val mimeType = context.contentResolver.getType(it.uri)
-            if ((mimeType != null) && mimeType.startsWith("image")) {
+            if (it.mimeType.startsWith("image")) {
                 AsyncImage(
                     model = ImageRequest.Builder(LocalContext.current)
                         .data(it.uri)
@@ -330,7 +347,32 @@ fun AttachmentCaption(navController: NavHostController, attachments: AttachmentI
                         if (textInput.text.trim().isEmpty()) {
                             return@IconButton
                         }
+                        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 = "",
+                                        thumb_id = item.name,
+                                        image_id = item.name
+                                    ),
+                                    al.toList()
+                                )
+                            }
+                        }
                         textInput = TextFieldValue("")
+                        navController.popBackStack()
                     }) {
                         Icon(
                             imageVector = Icons.AutoMirrored.Filled.Send,

+ 7 - 5
app/src/main/java/io/nexilis/alpha/ui/main/Chat.kt

@@ -51,6 +51,9 @@ fun Chat(
     val list by messageModel.getOpposite(pin).observeAsState()
     val state = rememberLazyListState()
     val context = LocalContext.current
+    LaunchedEffect(key1 = list) {
+        list?.size?.minus(1)?.let { state.scrollToItem(it) }
+    }
     Column(
         modifier = Modifier
             .fillMaxSize()
@@ -59,8 +62,7 @@ fun Chat(
     ) {
         LazyColumn(
             modifier = Modifier.weight(1.0f),
-            state = state,
-            reverseLayout = true
+            state = state
         ) {
             items(count = list?.size ?: 0) {
                 list?.let { l ->
@@ -124,7 +126,7 @@ fun Chat(
                         if (items.size == 1) {
                             val params = AttachmentItemList(items)
                             val data = Uri.encode(Json.encodeToString(params))
-                            navController.navigate(Screen.AttachmentCaption.route + "/${data}") {
+                            navController.navigate(Screen.AttachmentCaption.route + "?data=${data}&pin=${pin}") {
                                 launchSingleTop = true
                                 restoreState = true
                             }
@@ -156,8 +158,8 @@ fun Chat(
                                     message_text = textInput.text,
                                     opposite_pin = pin,
                                     f_display_name = it.first_name + " " + it.last_name,
-                                    message_large_text = textInput.text,
-                                    message_text_plain = textInput.text
+                                    message_large_text = "",
+                                    message_text_plain = ""
                                 )
                             )
                             textInput = TextFieldValue("")

+ 17 - 10
app/src/main/java/io/nexilis/alpha/ui/main/Main.kt

@@ -46,7 +46,8 @@ fun Main(navController: NavHostController, me: Buddy) {
         topBar = {
             TopAppBar(
                 title = {
-                    Title( navController = navController,
+                    Title(
+                        navController = navController,
                         navMainController = navMainController
                     )
                 },
@@ -115,16 +116,22 @@ fun Main(navController: NavHostController, me: Buddy) {
                     }
                 }
                 composable(
-                    route = Screen.AttachmentCaption.route + "/{data}",
-                    arguments = listOf(navArgument("data") { type = AttachmentItemListType })
+                    route = Screen.AttachmentCaption.route + "?data={data}&pin={pin}",
+                    arguments = listOf(
+                        navArgument("data") { type = AttachmentItemListType },
+                        navArgument("pin") { type = NavType.StringType }
+                    )
                 ) {
-                    it.arguments?.getParcelable<AttachmentItemList>("data")
-                        ?.let { data ->
-                            AttachmentCaption(
-                                navController = navMainController,
-                                attachments = data
-                            )
-                        }
+                    it.arguments?.getString("pin")?.let { pin ->
+                        it.arguments?.getParcelable<AttachmentItemList>("data")
+                            ?.let { data ->
+                                AttachmentCaption(
+                                    navController = navMainController,
+                                    pin = pin,
+                                    attachments = data
+                                )
+                            }
+                    }
                 }
             }
         }

+ 1 - 1
cpaas-lite/src/main/java/io/nexilis/service/Api.kt

@@ -101,7 +101,7 @@ class Api {
 
         internal fun sendToken(context: Context, token: String) {
             if (context.getSharedPreferences().getBoolean("is_login", false)) {
-                context.getSharedPreferences().getString("pin", "")?.let {
+                context.getSharedPreferences().getString("pin", null)?.let {
                     apiScope.launch {
                         Service.sendAsync(
                             Data(

+ 57 - 0
cpaas-lite/src/main/java/io/nexilis/service/core/Extension.kt

@@ -2,9 +2,16 @@ package io.nexilis.service.core
 
 import android.content.Context
 import android.content.SharedPreferences
+import android.net.Uri
+import android.provider.OpenableColumns
+import android.util.Log
+import android.webkit.MimeTypeMap
 import androidx.compose.ui.graphics.Color
 import io.nexilis.service.namePreference
+import io.nexilis.service.tag
 import java.io.File
+import java.io.FileOutputStream
+import java.io.InputStream
 import java.net.URLDecoder
 import java.net.URLEncoder
 import java.nio.charset.StandardCharsets
@@ -50,6 +57,31 @@ fun Context.createImageFile(): File {
     )
 }
 
+fun Uri.getFileName(context: Context): String? {
+    val item = context.contentResolver.query(this, null, null, null, null)
+        ?.use { cursor ->
+            val nameIndex =
+                cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+            cursor.moveToFirst()
+            cursor.getString(nameIndex)
+        }
+    return item
+}
+
+fun Context.getMimeType(uri: Uri): String {
+    Log.d(tag, "getMimeType:uri:$uri")
+    val fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString())
+    if (!fileExtension.isNullOrEmpty()) {
+        Log.d(tag, "getMimeType:fileExtension:$fileExtension")
+        MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+            fileExtension.lowercase(Locale.getDefault())
+        )?.let {
+            return it
+        }
+    }
+    return contentResolver.getType(uri) ?: ""
+}
+
 fun String.toMD5(): String {
     val md = MessageDigest.getInstance("MD5")
     val hashInBytes = md.digest(this.toByteArray(StandardCharsets.UTF_8))
@@ -76,6 +108,31 @@ fun Context.getSharedPreferences(): SharedPreferences {
     )
 }
 
+fun InputStream.toFile(context: Context, name: String? = null): File {
+    val file = this.use { input ->
+        var fileName = name
+        if (fileName == null) {
+            val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(
+                Date()
+            )
+            fileName = "FL_" + timeStamp + "_"
+        }
+        val file = File(context.filesDir, fileName)
+        val outputStream = FileOutputStream(file)
+        outputStream.use { output ->
+            val buffer = ByteArray(4 * 1024)
+            while (true) {
+                val byteCount = input.read(buffer)
+                if (byteCount < 0) break
+                output.write(buffer, 0, byteCount)
+            }
+            output.flush()
+        }
+        file
+    }
+    return file
+}
+
 val Color.Companion.Purple: Color
     get() = Color(0xFF800080)
 val Color.Companion.Orange: Color

+ 6 - 6
cpaas-lite/src/main/java/io/nexilis/service/core/Incoming.kt

@@ -121,7 +121,7 @@ class Incoming(private val context: Context) {
 
     private suspend fun pushMySelf(data: Data) {
         try {
-            val me = context.getSharedPreferences().getString("pin", "")
+            val me = context.getSharedPreferences().getString("pin", null)
             val dao = ApiRoomDatabase.getDatabase(context).buddyDao()
             dao.deleteMe()
             dao.insert(
@@ -205,7 +205,7 @@ class Incoming(private val context: Context) {
 
     private suspend fun pushBuddies(data: Data) {
         try {
-            val me = context.getSharedPreferences().getString("pin", "")
+            val me = context.getSharedPreferences().getString("pin", null)
             val d = data.bodies["A112"] ?: ""
             val jsonArray = JSONArray(d)
             Log.d(tag, "pushBuddies:$jsonArray")
@@ -293,7 +293,7 @@ class Incoming(private val context: Context) {
 
     private suspend fun pushGroupBatch(data: Data) {
         try {
-            val me = context.getSharedPreferences().getString("pin", "")
+            val me = context.getSharedPreferences().getString("pin", null)
             val d = data.bodies["A112"] ?: ""
             val jsonArray = JSONArray(d)
             for (i in 0 until jsonArray.length()) {
@@ -351,7 +351,7 @@ class Incoming(private val context: Context) {
 
     private suspend fun pushMember(data: Data) {
         try {
-            val me = context.getSharedPreferences().getString("pin", "")
+            val me = context.getSharedPreferences().getString("pin", null)
             ApiRoomDatabase.getDatabase(context).groupMemberDao().insert(
                 GroupMember(
                     data.bodies["A04"] ?: "",
@@ -377,7 +377,7 @@ class Incoming(private val context: Context) {
         try {
             val messageId = data.bodies["A18"] ?: ""
             if (messageId.isEmpty()) return
-            val me = context.getSharedPreferences().getString("pin", "")
+            val me = context.getSharedPreferences().getString("pin", null)
             ApiRoomDatabase.getDatabase(context).messageDao().getSync(messageId)?.let {
                 Service.sendAck(me, data.status)
                 return
@@ -536,7 +536,7 @@ class Incoming(private val context: Context) {
 
     private suspend fun statusMessage(data: Data) {
         try {
-            val me = context.getSharedPreferences().getString("pin", "")
+            val me = context.getSharedPreferences().getString("pin", null)
             val messageIds = data.bodies["A18"] ?: ""
             if (messageIds.isEmpty()) return
             val status = data.bodies["A15"]?.toInt() ?: 0

+ 32 - 14
cpaas-lite/src/main/java/io/nexilis/service/core/Network.kt

@@ -4,9 +4,7 @@ import android.content.Context
 import android.net.Uri
 import android.os.Build
 import android.util.Log
-import android.webkit.MimeTypeMap
 import androidx.core.content.FileProvider
-import androidx.core.net.toUri
 import io.nexilis.service.tag
 import okhttp3.Call
 import okhttp3.Callback
@@ -22,8 +20,6 @@ import okio.buffer
 import okio.sink
 import java.io.File
 import java.io.IOException
-import java.nio.file.Files
-import java.util.Locale
 
 
 class Network {
@@ -35,17 +31,16 @@ class Network {
                 .setType(MultipartBody.FORM)
                 .addFormDataPart(
                     "file", file.name,
-                    file.asRequestBody(Files.probeContentType(file.toPath()).toMediaTypeOrNull())
+                    file.asRequestBody(null)
                 )
                 .addFormDataPart("other_field", "other_field_value")
                 .build()
         } else {
-            file.toUri()
             MultipartBody.Builder()
                 .setType(MultipartBody.FORM)
                 .addFormDataPart(
                     "file", file.name,
-                    file.asRequestBody(getMimeType(file.toUri()).toMediaTypeOrNull())
+                    file.asRequestBody(null)
                 )
                 .addFormDataPart("other_field", "other_field_value")
                 .build()
@@ -70,14 +65,37 @@ class Network {
         })
     }
 
-    private fun getMimeType(uri: Uri): String {
-        val fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString())
-        MimeTypeMap.getSingleton().getMimeTypeFromExtension(
-            fileExtension.lowercase(Locale.getDefault())
-        )?.let {
-            return it
+    fun upload(url: String, file: File) : Boolean {
+        val client = OkHttpClient()
+        val body: RequestBody = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            MultipartBody.Builder()
+                .setType(MultipartBody.FORM)
+                .addFormDataPart(
+                    "file", file.name,
+                    file.asRequestBody(null)
+                )
+                .addFormDataPart("other_field", "other_field_value")
+                .build()
+        } else {
+            MultipartBody.Builder()
+                .setType(MultipartBody.FORM)
+                .addFormDataPart(
+                    "file", file.name,
+                    file.asRequestBody(null)
+                )
+                .addFormDataPart("other_field", "other_field_value")
+                .build()
+        }
+        val request: Request = Request.Builder()
+            .url(url)
+            .addHeader("User-Agent", "Mozilla/5.0")
+            .addHeader("Cookie", "PHPSESSID=123;MOBILE=123")
+            .post(body)
+            .build()
+        Log.d(tag, "upload:execute:$url:${file.name}")
+        client.newCall(request).execute().use { response ->
+            return response.code == 200
         }
-        return "text/plain"
     }
 
     fun post(url: String, body: String, completion: (Boolean, body: String?) -> Unit) {

+ 80 - 9
cpaas-lite/src/main/java/io/nexilis/service/core/Outgoing.kt

@@ -6,6 +6,7 @@ import android.app.NotificationChannel
 import android.app.NotificationManager
 import android.content.Context
 import android.os.Build
+import android.util.Log
 import androidx.annotation.StringRes
 import androidx.core.app.NotificationCompat
 import androidx.work.CoroutineWorker
@@ -15,24 +16,94 @@ import androidx.work.WorkerParameters
 import androidx.work.workDataOf
 import io.nexilis.service.R
 import io.nexilis.service.Service
+import io.nexilis.service.data.rooms.ApiRoomDatabase
+import io.nexilis.service.tag
+import java.io.File
 
-class Outgoing(context: Context, parameters: WorkerParameters) :
+class Outgoing(private val context: Context, parameters: WorkerParameters) :
     CoroutineWorker(context, parameters) {
     override suspend fun doWork(): Result {
         try {
             inputData.getString("data")?.let {
-                Service.sendSync(it)?.let { data ->
-                    return Result.success(
-                        workDataOf(
-                            "message_id" to data.bodies["A18"],
-                            "opposite_pin" to data.bodies["A01"],
-                            "status" to data.bodies["A15"]?.toInt()
-                        )
+                ApiRoomDatabase.getDatabase(context).messageDao().getSync(it)?.let { message ->
+                    val bodies = mutableMapOf(
+                        "A18" to message.message_id,
+                        "A00" to message.f_pin,
+                        "A01" to message.l_pin,
+                        "A19" to message.server_date.toString(),
+                        "lcltm" to message.local_timestamp.toString(),
+                        "A06" to message.message_scope_id,
+                        "A15" to message.status,
+                        "A07" to message.message_text.toStupidString(),
+                        "A118" to message.credential,
+                        "A149" to message.attachment_flag.toString(),
+                        "A52" to message.blog_id,
+                        "ics" to message.is_consult.toString(),
+                        "icc" to message.is_call_center.toString(),
+                        "ccid" to message.call_center_id,
+                        "iwm" to message.is_work_mode.toString(),
+                        "Bf" to message.read_receipts.toString()
+                    )
+                    if (message.message_scope_id == "5") {
+                        bodies["BR"] = message.f_display_name
+                    }
+                    if (message.contact.isNotEmpty()) {
+                        bodies["A116"] = message.contact
+                    }
+                    if (message.image_id.isNotEmpty()) {
+                        bodies["A57"] = message.image_id
+                    }
+                    if (message.audio_id.isNotEmpty()) {
+                        bodies["A63"] = message.audio_id
+                    }
+                    if (message.video_id.isNotEmpty()) {
+                        bodies["A47"] = message.video_id
+                    }
+                    if (message.file_id.isNotEmpty()) {
+                        bodies["BN"] = message.file_id
+                    }
+                    if (message.thumb_id.isNotEmpty()) {
+                        bodies["A74"] = message.thumb_id
+                    }
+                    if (message.reff_id.isNotEmpty()) {
+                        bodies["A121"] = message.reff_id
+                    }
+                    if (message.chat_id.isNotEmpty()) {
+                        bodies["BA"] = message.chat_id
+                    }
+                    if (message.broadcast_flag > 0) {
+                        bodies["B4"] = message.broadcast_flag.toString()
+                    }
+                    val data = Data(
+                        code = "S0",
+                        status = message.message_id,
+                        f_pin = message.f_pin,
+                        bodies = bodies
                     )
+                    Log.d(tag, "message.image_id:${message.image_id}")
+                    if (message.image_id.isNotEmpty()) {
+                        val upload = Network().upload(
+                            "https://digixplatform.com/uploader",
+                            File(context.filesDir, message.image_id)
+                        )
+                        if (!upload) {
+                            Log.d(tag, "Failed to upload image:${message.message_id}")
+                            return Result.retry()
+                        }
+                    }
+                    Service.sendSync(data)?.let { r ->
+                        return Result.success(
+                            workDataOf(
+                                "message_id" to r.bodies["A18"],
+                                "opposite_pin" to r.bodies["A01"],
+                                "status" to r.bodies["A15"]?.toInt()
+                            )
+                        )
+                    }
                 }
             }
         } catch (e: Exception) {
-            Result.failure()
+            return Result.failure()
         }
         return Result.retry()
     }

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

@@ -19,7 +19,7 @@ abstract class MessageDao {
     @Query("select * from Message where message_id = :id")
     abstract fun getSync(id: String): Message?
 
-    @Query("select * from Message where opposite_pin = :pin order by server_date desc")
+    @Query("select * from Message where opposite_pin = :pin order by server_date asc")
     abstract fun getOpposite(pin: String): LiveData<List<Message>>
 
     @Insert(onConflict = OnConflictStrategy.REPLACE)

+ 1 - 1
cpaas-lite/src/main/java/io/nexilis/service/data/entities/Message.kt

@@ -37,7 +37,7 @@ data class Message(
     val credential: String = "",
     val attachment_flag: Int = 0,
     val is_stared: Int = 0,
-    val f_display_name: String,
+    val f_display_name: String = "",
     val reff_id: String = "",
     val sent_qty: Int = 0,
     val delivered_qty: Int = 0,

+ 2 - 57
cpaas-lite/src/main/java/io/nexilis/service/data/repositories/MessageRepository.kt

@@ -9,9 +9,7 @@ import androidx.work.OutOfQuotaPolicy
 import androidx.work.WorkManager
 import androidx.work.WorkRequest
 import androidx.work.workDataOf
-import io.nexilis.service.core.Data
 import io.nexilis.service.core.Outgoing
-import io.nexilis.service.core.toStupidString
 import io.nexilis.service.data.daos.GroupMemberDao
 import io.nexilis.service.data.daos.MessageDao
 import io.nexilis.service.data.daos.MessageStatusDao
@@ -81,67 +79,14 @@ class MessageRepository @Inject constructor(
                 )
             )
         }
-        val bodies = mutableMapOf(
-            "A18" to message.message_id,
-            "A00" to message.f_pin,
-            "A01" to message.l_pin,
-            "A19" to message.server_date.toString(),
-            "lcltm" to message.local_timestamp.toString(),
-            "A06" to message.message_scope_id,
-            "A15" to message.status,
-            "A07" to message.message_text.toStupidString(),
-            "A118" to message.credential,
-            "A149" to message.attachment_flag.toString(),
-            "A52" to message.blog_id,
-            "ics" to message.is_consult.toString(),
-            "icc" to message.is_call_center.toString(),
-            "ccid" to message.call_center_id,
-            "iwm" to message.is_work_mode.toString(),
-            "Bf" to message.read_receipts.toString()
-        )
-        if (message.message_scope_id == "5") {
-            bodies["BR"] = message.f_display_name
-        }
-        if (message.contact.isNotEmpty()) {
-            bodies["A116"] = message.contact
-        }
-        if (message.image_id.isNotEmpty()) {
-            bodies["A57"] = message.image_id
-        }
-        if (message.audio_id.isNotEmpty()) {
-            bodies["A63"] = message.audio_id
-        }
-        if (message.video_id.isNotEmpty()) {
-            bodies["A47"] = message.video_id
-        }
-        if (message.file_id.isNotEmpty()) {
-            bodies["BN"] = message.file_id
-        }
-        if (message.thumb_id.isNotEmpty()) {
-            bodies["A74"] = message.thumb_id
-        }
-        if (message.reff_id.isNotEmpty()) {
-            bodies["A121"] = message.reff_id
-        }
-        if (message.chat_id.isNotEmpty()) {
-            bodies["BA"] = message.chat_id
-        }
-        if (message.broadcast_flag > 0) {
-            bodies["B4"] = message.broadcast_flag.toString()
-        }
-        val data = Data(
-            code = "S0",
-            status = message.message_id,
-            f_pin = message.f_pin,
-            bodies = bodies
-        )
+        Log.d("SAPI", "WorkManager:enqueue:${message.message_id}")
         val id = UUID.randomUUID()
         val workRequest: WorkRequest =
             OneTimeWorkRequestBuilder<Outgoing>()
                 .setId(id)
                 .setInputData(
                     workDataOf(
-                        "data" to data.toString()
+                        "data" to message.message_id
                     )
                 )
                 .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)

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

@@ -1,10 +1,14 @@
 package io.nexilis.service.data.viewmodels
 
 import android.content.Context
+import android.net.Uri
+import android.util.Log
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewModelScope
 import dagger.hilt.android.lifecycle.HiltViewModel
+import io.nexilis.service.core.getFileName
+import io.nexilis.service.core.toFile
 import io.nexilis.service.data.daos.MessageStatusDao
 import io.nexilis.service.data.entities.Message
 import io.nexilis.service.data.repositories.MessageRepository
@@ -35,9 +39,16 @@ class MessageViewModel @Inject constructor(
 
     fun send(
         context: Context,
-        entity: Message
+        entity: Message,
+        files: List<Uri>? = null
     ) =
         viewModelScope.launch(Dispatchers.IO) {
+            files?.forEach { uri ->
+                context.contentResolver.openInputStream(uri).use { input ->
+                    input?.toFile(context, uri.getFileName(context))
+                }
+            }
+            Log.d("SAPI", "send:files:$files")
             repository.send(context, entity) { id, pin, status ->
                 viewModelScope.launch(Dispatchers.IO) {
                     messageStatusDao.updateStatus(