@@ -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,8 @@ 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
4043import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
4144import com.nextcloud.talk.models.MessageDraft
4245import com.nextcloud.talk.models.domain.ConversationModel
@@ -103,7 +106,9 @@ import retrofit2.HttpException
103106import java.io.File
104107import java.io.IOException
105108import java.time.LocalDate
109+ import java.util.UUID
106110import javax.inject.Inject
111+ import androidx.core.net.toUri
107112
108113@Suppress(" TooManyFunctions" , " LongParameterList" )
109114class ChatViewModel @AssistedInject constructor(
@@ -148,6 +153,21 @@ class ChatViewModel @AssistedInject constructor(
148153 var hiddenUpcomingEvent: String? = null
149154 lateinit var participantPermissions: ParticipantPermissions
150155
156+ private val _uploadProgressMap = MutableStateFlow <Map <String , Int >>(emptyMap())
157+ val uploadProgressMap: StateFlow <Map <String , Int >> = _uploadProgressMap
158+
159+ // Maps referenceId -> fileUri for cancellation support
160+ private val uploadReferenceToUri = mutableMapOf<String , String >()
161+
162+ fun cancelUpload (referenceId : String ) {
163+ val fileUri = uploadReferenceToUri.remove(referenceId) ? : return
164+ WorkManager .getInstance(NextcloudTalkApplication .sharedApplication!! ).cancelUniqueWork(fileUri)
165+ viewModelScope.launch {
166+ chatRepository.deleteTempMessageByReferenceId(referenceId)
167+ }
168+ _uploadProgressMap .update { it - referenceId }
169+ }
170+
151171 fun getChatRepository (): ChatMessageRepository = chatRepository
152172
153173 override fun onResume (owner : LifecycleOwner ) {
@@ -1413,23 +1433,82 @@ class ChatViewModel @AssistedInject constructor(
14131433 metaDataMap[" caption" ] = caption
14141434 }
14151435
1436+ val referenceId = UUID .randomUUID().toString().replace(" -" , " " )
1437+ metaDataMap[" referenceId" ] = referenceId
1438+
14161439 val metaData = Gson ().toJson(metaDataMap)
14171440
14181441 room = if (roomToken == " " ) chatRoomToken else roomToken
14191442
14201443 try {
14211444 require(fileUri.isNotEmpty())
1422- UploadAndShareFilesWorker .upload(
1445+
1446+ if (! isVoiceMessage) {
1447+ val (fileName, mimeType, fileSize) = resolveFileInfo(fileUri)
1448+ viewModelScope.launch {
1449+ chatRepository.addUploadPlaceholderMessage(
1450+ localFileUri = fileUri,
1451+ caption = caption.ifEmpty { fileName },
1452+ mimeType = mimeType,
1453+ fileSize = fileSize,
1454+ referenceId = referenceId
1455+ ).collect {}
1456+ }
1457+ }
1458+
1459+ val internalConversationId = " ${currentUser.id} @$chatRoomToken "
1460+ val workerId = UploadAndShareFilesWorker .upload(
14231461 fileUri,
14241462 room,
14251463 displayName,
1426- metaData
1464+ metaData,
1465+ referenceId,
1466+ internalConversationId
14271467 )
1468+
1469+ if (! isVoiceMessage) {
1470+ uploadReferenceToUri[referenceId] = fileUri
1471+ observeUploadProgress(workerId, referenceId)
1472+ }
14281473 } catch (e: IllegalArgumentException ) {
14291474 Log .e(javaClass.simpleName, " Something went wrong when trying to upload file" , e)
14301475 }
14311476 }
14321477
1478+ private fun resolveFileInfo (fileUri : String ): Triple <String , String ?, Long > {
1479+ val uri = fileUri.toUri()
1480+ val mimeType = NextcloudTalkApplication .sharedApplication!! .contentResolver.getType(uri)
1481+ val cursor = NextcloudTalkApplication .sharedApplication!! .contentResolver.query(uri, null , null , null , null )
1482+ cursor?.use {
1483+ val nameIndex = it.getColumnIndex(OpenableColumns .DISPLAY_NAME )
1484+ val sizeIndex = it.getColumnIndex(OpenableColumns .SIZE )
1485+ if (it.moveToFirst()) {
1486+ val name = if (nameIndex >= 0 ) it.getString(nameIndex).orEmpty() else uri.lastPathSegment.orEmpty()
1487+ val size = if (sizeIndex >= 0 ) it.getLong(sizeIndex) else 0L
1488+ return Triple (name, mimeType, size)
1489+ }
1490+ }
1491+ return Triple (uri.lastPathSegment.orEmpty(), mimeType, 0L )
1492+ }
1493+
1494+ private fun observeUploadProgress (workerId : UUID , referenceId : String ) {
1495+ WorkManager .getInstance(NextcloudTalkApplication .sharedApplication!! )
1496+ .getWorkInfoByIdLiveData(workerId)
1497+ .asFlow()
1498+ .onEach { workInfo ->
1499+ if (workInfo == null ) return @onEach
1500+ val progress = workInfo.progress.getInt(UploadAndShareFilesWorker .PROGRESS_KEY , - 1 )
1501+ if (progress >= 0 ) {
1502+ _uploadProgressMap .update { it + (referenceId to progress) }
1503+ }
1504+ if (workInfo.state.isFinished) {
1505+ _uploadProgressMap .update { it - referenceId }
1506+ uploadReferenceToUri.remove(referenceId)
1507+ }
1508+ }
1509+ .launchIn(viewModelScope)
1510+ }
1511+
14331512 fun postToRecordTouchObserver (float : Float ) {
14341513 _recordTouchObserver .postValue(float)
14351514 }
0 commit comments