attachments: proper metadata plumbing
This commit is contained in:
@@ -2,55 +2,23 @@ package net.buzzert.kordophonedroid.ui.messagelist
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.webkit.MimeTypeMap
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.core.net.toFile
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import coil.Coil
|
|
||||||
import coil.ComponentRegistry
|
|
||||||
import coil.ImageLoader
|
|
||||||
import coil.ImageLoaderFactory
|
|
||||||
import coil.decode.DataSource
|
|
||||||
import coil.decode.ImageSource
|
|
||||||
import coil.disk.DiskCache
|
|
||||||
import coil.fetch.FetchResult
|
|
||||||
import coil.fetch.Fetcher
|
|
||||||
import coil.fetch.SourceResult
|
|
||||||
import coil.memory.MemoryCache
|
|
||||||
import coil.request.CachePolicy
|
|
||||||
import coil.request.Options
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import kotlinx.coroutines.channels.consumeEach
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asFlow
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.flow.flowOf
|
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.buzzert.kordophone.backend.model.Conversation
|
import net.buzzert.kordophone.backend.model.Conversation
|
||||||
import net.buzzert.kordophone.backend.model.GUID
|
import net.buzzert.kordophone.backend.model.GUID
|
||||||
import net.buzzert.kordophone.backend.model.Message
|
import net.buzzert.kordophone.backend.model.Message
|
||||||
import net.buzzert.kordophone.backend.model.OutgoingMessage
|
import net.buzzert.kordophone.backend.model.OutgoingMessage
|
||||||
|
import net.buzzert.kordophone.backend.model.UploadingAttachmentMetadata
|
||||||
import net.buzzert.kordophone.backend.server.ChatRepository
|
import net.buzzert.kordophone.backend.server.ChatRepository
|
||||||
import net.buzzert.kordophonedroid.ui.attachments.AttachmentFetchData
|
|
||||||
import net.buzzert.kordophonedroid.ui.attachments.AttachmentViewModel
|
|
||||||
import okio.ByteString.Companion.toByteString
|
|
||||||
import java.io.BufferedInputStream
|
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.nio.file.FileSystem
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.UUID
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.xml.transform.Source
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
const val MVM_LOG: String = "MessageListViewModel"
|
const val MVM_LOG: String = "MessageListViewModel"
|
||||||
|
|
||||||
@@ -106,7 +74,22 @@ class MessageListViewModel @Inject constructor(
|
|||||||
conversation = conversation!!,
|
conversation = conversation!!,
|
||||||
attachmentUris = attachmentUris,
|
attachmentUris = attachmentUris,
|
||||||
attachmentDataSource = { uri ->
|
attachmentDataSource = { uri ->
|
||||||
context.contentResolver.openInputStream(uri)
|
val resolver = context.contentResolver
|
||||||
|
val inputStream = resolver.openInputStream(uri)
|
||||||
|
val mimeType = resolver.getType(uri)
|
||||||
|
|
||||||
|
val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: "jpg"
|
||||||
|
val filename = uri.lastPathSegment + ".$extension"
|
||||||
|
|
||||||
|
if (inputStream != null && mimeType != null) {
|
||||||
|
UploadingAttachmentMetadata(
|
||||||
|
inputStream = inputStream,
|
||||||
|
mimeType = mimeType,
|
||||||
|
filename = filename
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -71,11 +71,17 @@ data class Message(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class UploadingAttachmentMetadata(
|
||||||
|
val inputStream: InputStream,
|
||||||
|
val mimeType: String,
|
||||||
|
val filename: String,
|
||||||
|
)
|
||||||
|
|
||||||
data class OutgoingMessage(
|
data class OutgoingMessage(
|
||||||
val body: String,
|
val body: String,
|
||||||
val conversation: Conversation,
|
val conversation: Conversation,
|
||||||
val attachmentUris: Set<Uri>,
|
val attachmentUris: Set<Uri>,
|
||||||
val attachmentDataSource: (Uri) -> InputStream?
|
val attachmentDataSource: (Uri) -> UploadingAttachmentMetadata?
|
||||||
) {
|
) {
|
||||||
val guid: String = UUID.randomUUID().toString()
|
val guid: String = UUID.randomUUID().toString()
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +1,37 @@
|
|||||||
package net.buzzert.kordophone.backend.server
|
package net.buzzert.kordophone.backend.server
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import androidx.core.net.toFile
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
|
||||||
import kotlinx.coroutines.channels.consumeEach
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.collect
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import net.buzzert.kordophone.backend.db.CachedChatDatabase
|
import net.buzzert.kordophone.backend.db.CachedChatDatabase
|
||||||
import net.buzzert.kordophone.backend.events.MessageDeliveredEvent
|
import net.buzzert.kordophone.backend.events.MessageDeliveredEvent
|
||||||
import net.buzzert.kordophone.backend.model.Conversation
|
import net.buzzert.kordophone.backend.model.Conversation
|
||||||
import net.buzzert.kordophone.backend.model.GUID
|
import net.buzzert.kordophone.backend.model.GUID
|
||||||
import net.buzzert.kordophone.backend.model.Message
|
import net.buzzert.kordophone.backend.model.Message
|
||||||
import net.buzzert.kordophone.backend.model.OutgoingMessage
|
import net.buzzert.kordophone.backend.model.OutgoingMessage
|
||||||
import net.buzzert.kordophone.backend.model.UpdateItem
|
|
||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import okio.BufferedSource
|
import okio.BufferedSource
|
||||||
import java.io.File
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.lang.Error
|
|
||||||
import java.net.URL
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Queue
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.ArrayBlockingQueue
|
import java.util.concurrent.ArrayBlockingQueue
|
||||||
import java.util.concurrent.CancellationException
|
import kotlin.Boolean
|
||||||
|
import kotlin.Int
|
||||||
|
import kotlin.String
|
||||||
|
import kotlin.let
|
||||||
|
|
||||||
const val REPO_LOG: String = "ChatRepository"
|
const val REPO_LOG: String = "ChatRepository"
|
||||||
const val CONVERSATION_MESSAGE_SYNC_COUNT = 10
|
const val CONVERSATION_MESSAGE_SYNC_COUNT = 10
|
||||||
@@ -210,7 +202,10 @@ class ChatRepository(
|
|||||||
private suspend fun uploadAttachment(filename: String, mediaType: String, source: InputStream): String {
|
private suspend fun uploadAttachment(filename: String, mediaType: String, source: InputStream): String {
|
||||||
val attachmentData = source.readBytes()
|
val attachmentData = source.readBytes()
|
||||||
val requestBody = RequestBody.create(MediaType.get(mediaType), attachmentData)
|
val requestBody = RequestBody.create(MediaType.get(mediaType), attachmentData)
|
||||||
source.close()
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
source.close()
|
||||||
|
}
|
||||||
|
|
||||||
val response = apiInterface.uploadAttachment(filename, requestBody)
|
val response = apiInterface.uploadAttachment(filename, requestBody)
|
||||||
return response.bodyOnSuccessOrThrow().transferGUID
|
return response.bodyOnSuccessOrThrow().transferGUID
|
||||||
@@ -237,7 +232,7 @@ class ChatRepository(
|
|||||||
.onEach { it.conversation = conversation }
|
.onEach { it.conversation = conversation }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleConversationChangedUpdate(conversation: Conversation) {
|
private fun handleConversationChangedUpdate(conversation: Conversation) {
|
||||||
Log.d(REPO_LOG, "Handling conversation changed update")
|
Log.d(REPO_LOG, "Handling conversation changed update")
|
||||||
database.writeConversations(listOf(conversation))
|
database.writeConversations(listOf(conversation))
|
||||||
}
|
}
|
||||||
@@ -276,13 +271,10 @@ class ChatRepository(
|
|||||||
val attachmentGUIDs = mutableListOf<String>()
|
val attachmentGUIDs = mutableListOf<String>()
|
||||||
try {
|
try {
|
||||||
for (uri: Uri in it.attachmentUris) {
|
for (uri: Uri in it.attachmentUris) {
|
||||||
val inputStream = it.attachmentDataSource(uri)
|
val uploadData = it.attachmentDataSource(uri)
|
||||||
?: throw java.lang.Exception("No input stream")
|
?: throw java.lang.Exception("No upload data.")
|
||||||
|
|
||||||
val filename = uri.lastPathSegment ?: "attachment.png"
|
|
||||||
val mediaType = "image/png" // TODO: Actually get this: it needs to be plumbed through ContentResolver
|
|
||||||
val guid = uploadAttachment(filename, mediaType, inputStream)
|
|
||||||
|
|
||||||
|
val guid = uploadAttachment(uploadData.filename, uploadData.mimeType, uploadData.inputStream)
|
||||||
attachmentGUIDs.add(guid)
|
attachmentGUIDs.add(guid)
|
||||||
}
|
}
|
||||||
} catch (e: java.lang.Exception) {
|
} catch (e: java.lang.Exception) {
|
||||||
|
|||||||
Reference in New Issue
Block a user