@@ -10,6 +10,7 @@ package com.nextcloud.talk.chat.viewmodels
1010import android.content.Context
1111import android.net.Uri
1212import android.os.Bundle
13+ import android.provider.OpenableColumns
1314import android.util.Log
1415import androidx.lifecycle.DefaultLifecycleObserver
1516import androidx.lifecycle.LifecycleOwner
@@ -37,6 +38,9 @@ import com.nextcloud.talk.data.database.mappers.toDomainModel
3738import com.nextcloud.talk.data.database.model.ChatMessageEntity
3839import com.nextcloud.talk.data.user.model.User
3940import com.nextcloud.talk.extensions.toIntOrZero
41+ import androidx.lifecycle.asFlow
42+ import androidx.work.WorkManager
43+ import com.nextcloud.talk.application.NextcloudTalkApplication
4044import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
4145import com.nextcloud.talk.models.MessageDraft
4246import com.nextcloud.talk.models.domain.ConversationModel
@@ -103,7 +107,9 @@ import retrofit2.HttpException
103107import java.io.File
104108import java.io.IOException
105109import java.time.LocalDate
110+ import java.util.UUID
106111import javax.inject.Inject
112+ import androidx.core.net.toUri
107113
108114@Suppress(" TooManyFunctions" , " LongParameterList" )
109115class ChatViewModel @AssistedInject constructor(
@@ -148,6 +154,21 @@ class ChatViewModel @AssistedInject constructor(
148154 var hiddenUpcomingEvent: String? = null
149155 lateinit var participantPermissions: ParticipantPermissions
150156
157+ private val _uploadProgressMap = MutableStateFlow <Map <String , Int >>(emptyMap())
158+ val uploadProgressMap: StateFlow <Map <String , Int >> = _uploadProgressMap
159+
160+ // Maps referenceId -> fileUri for cancellation support
161+ private val uploadReferenceToUri = mutableMapOf<String , String >()
162+
163+ fun cancelUpload (referenceId : String ) {
164+ val fileUri = uploadReferenceToUri.remove(referenceId) ? : return
165+ WorkManager .getInstance(NextcloudTalkApplication .sharedApplication!! ).cancelUniqueWork(fileUri)
166+ viewModelScope.launch {
167+ chatRepository.deleteTempMessageByReferenceId(referenceId)
168+ }
169+ _uploadProgressMap .update { it - referenceId }
170+ }
171+
151172 fun getChatRepository (): ChatMessageRepository = chatRepository
152173
153174 override fun onResume (owner : LifecycleOwner ) {
@@ -1413,23 +1434,82 @@ class ChatViewModel @AssistedInject constructor(
14131434 metaDataMap[" caption" ] = caption
14141435 }
14151436
1437+ val referenceId = UUID .randomUUID().toString().replace(" -" , " " )
1438+ metaDataMap[" referenceId" ] = referenceId
1439+
14161440 val metaData = Gson ().toJson(metaDataMap)
14171441
14181442 room = if (roomToken == " " ) chatRoomToken else roomToken
14191443
14201444 try {
14211445 require(fileUri.isNotEmpty())
1422- UploadAndShareFilesWorker .upload(
1446+
1447+ if (! isVoiceMessage) {
1448+ val (fileName, mimeType, fileSize) = resolveFileInfo(fileUri)
1449+ viewModelScope.launch {
1450+ chatRepository.addUploadPlaceholderMessage(
1451+ localFileUri = fileUri,
1452+ caption = caption.ifEmpty { fileName },
1453+ mimeType = mimeType,
1454+ fileSize = fileSize,
1455+ referenceId = referenceId
1456+ ).collect {}
1457+ }
1458+ }
1459+
1460+ val internalConversationId = " ${currentUser.id} @$chatRoomToken "
1461+ val workerId = UploadAndShareFilesWorker .upload(
14231462 fileUri,
14241463 room,
14251464 displayName,
1426- metaData
1465+ metaData,
1466+ referenceId,
1467+ internalConversationId
14271468 )
1469+
1470+ if (! isVoiceMessage) {
1471+ uploadReferenceToUri[referenceId] = fileUri
1472+ observeUploadProgress(workerId, referenceId)
1473+ }
14281474 } catch (e: IllegalArgumentException ) {
14291475 Log .e(javaClass.simpleName, " Something went wrong when trying to upload file" , e)
14301476 }
14311477 }
14321478
1479+ private fun resolveFileInfo (fileUri : String ): Triple <String , String ?, Long > {
1480+ val uri = fileUri.toUri()
1481+ val mimeType = NextcloudTalkApplication .sharedApplication!! .contentResolver.getType(uri)
1482+ val cursor = NextcloudTalkApplication .sharedApplication!! .contentResolver.query(uri, null , null , null , null )
1483+ cursor?.use {
1484+ val nameIndex = it.getColumnIndex(OpenableColumns .DISPLAY_NAME )
1485+ val sizeIndex = it.getColumnIndex(OpenableColumns .SIZE )
1486+ if (it.moveToFirst()) {
1487+ val name = if (nameIndex >= 0 ) it.getString(nameIndex).orEmpty() else uri.lastPathSegment.orEmpty()
1488+ val size = if (sizeIndex >= 0 ) it.getLong(sizeIndex) else 0L
1489+ return Triple (name, mimeType, size)
1490+ }
1491+ }
1492+ return Triple (uri.lastPathSegment.orEmpty(), mimeType, 0L )
1493+ }
1494+
1495+ private fun observeUploadProgress (workerId : UUID , referenceId : String ) {
1496+ WorkManager .getInstance(NextcloudTalkApplication .sharedApplication!! )
1497+ .getWorkInfoByIdLiveData(workerId)
1498+ .asFlow()
1499+ .onEach { workInfo ->
1500+ if (workInfo == null ) return @onEach
1501+ val progress = workInfo.progress.getInt(UploadAndShareFilesWorker .PROGRESS_KEY , - 1 )
1502+ if (progress >= 0 ) {
1503+ _uploadProgressMap .update { it + (referenceId to progress) }
1504+ }
1505+ if (workInfo.state.isFinished) {
1506+ _uploadProgressMap .update { it - referenceId }
1507+ uploadReferenceToUri.remove(referenceId)
1508+ }
1509+ }
1510+ .launchIn(viewModelScope)
1511+ }
1512+
14331513 fun postToRecordTouchObserver (float : Float ) {
14341514 _recordTouchObserver .postValue(float)
14351515 }
0 commit comments