@@ -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
@@ -35,6 +36,9 @@ import com.nextcloud.talk.data.database.mappers.toDomainModel
3536import com.nextcloud.talk.data.database.model.ChatMessageEntity
3637import com.nextcloud.talk.data.user.model.User
3738import com.nextcloud.talk.extensions.toIntOrZero
39+ import androidx.lifecycle.asFlow
40+ import androidx.work.WorkManager
41+ import com.nextcloud.talk.application.NextcloudTalkApplication
3842import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
3943import com.nextcloud.talk.models.MessageDraft
4044import com.nextcloud.talk.models.domain.ConversationModel
@@ -100,7 +104,9 @@ import retrofit2.HttpException
100104import java.io.File
101105import java.io.IOException
102106import java.time.LocalDate
107+ import java.util.UUID
103108import javax.inject.Inject
109+ import androidx.core.net.toUri
104110
105111@Suppress(" TooManyFunctions" , " LongParameterList" )
106112class ChatViewModel @AssistedInject constructor(
@@ -145,6 +151,21 @@ class ChatViewModel @AssistedInject constructor(
145151 var hiddenUpcomingEvent: String? = null
146152 lateinit var participantPermissions: ParticipantPermissions
147153
154+ private val _uploadProgressMap = MutableStateFlow <Map <String , Int >>(emptyMap())
155+ val uploadProgressMap: StateFlow <Map <String , Int >> = _uploadProgressMap
156+
157+ // Maps referenceId -> fileUri for cancellation support
158+ private val uploadReferenceToUri = mutableMapOf<String , String >()
159+
160+ fun cancelUpload (referenceId : String ) {
161+ val fileUri = uploadReferenceToUri.remove(referenceId) ? : return
162+ WorkManager .getInstance(NextcloudTalkApplication .sharedApplication!! ).cancelUniqueWork(fileUri)
163+ viewModelScope.launch {
164+ chatRepository.deleteTempMessageByReferenceId(referenceId)
165+ }
166+ _uploadProgressMap .update { it - referenceId }
167+ }
168+
148169 fun getChatRepository (): ChatMessageRepository = chatRepository
149170
150171 override fun onResume (owner : LifecycleOwner ) {
@@ -1392,23 +1413,82 @@ class ChatViewModel @AssistedInject constructor(
13921413 metaDataMap[" caption" ] = caption
13931414 }
13941415
1416+ val referenceId = UUID .randomUUID().toString().replace(" -" , " " )
1417+ metaDataMap[" referenceId" ] = referenceId
1418+
13951419 val metaData = Gson ().toJson(metaDataMap)
13961420
13971421 room = if (roomToken == " " ) chatRoomToken else roomToken
13981422
13991423 try {
14001424 require(fileUri.isNotEmpty())
1401- UploadAndShareFilesWorker .upload(
1425+
1426+ if (! isVoiceMessage) {
1427+ val (fileName, mimeType, fileSize) = resolveFileInfo(fileUri)
1428+ viewModelScope.launch {
1429+ chatRepository.addUploadPlaceholderMessage(
1430+ localFileUri = fileUri,
1431+ caption = caption.ifEmpty { fileName },
1432+ mimeType = mimeType,
1433+ fileSize = fileSize,
1434+ referenceId = referenceId
1435+ ).collect {}
1436+ }
1437+ }
1438+
1439+ val internalConversationId = " ${currentUser.id} @$chatRoomToken "
1440+ val workerId = UploadAndShareFilesWorker .upload(
14021441 fileUri,
14031442 room,
14041443 displayName,
1405- metaData
1444+ metaData,
1445+ referenceId,
1446+ internalConversationId
14061447 )
1448+
1449+ if (! isVoiceMessage) {
1450+ uploadReferenceToUri[referenceId] = fileUri
1451+ observeUploadProgress(workerId, referenceId)
1452+ }
14071453 } catch (e: IllegalArgumentException ) {
14081454 Log .e(javaClass.simpleName, " Something went wrong when trying to upload file" , e)
14091455 }
14101456 }
14111457
1458+ private fun resolveFileInfo (fileUri : String ): Triple <String , String ?, Long > {
1459+ val uri = fileUri.toUri()
1460+ val mimeType = NextcloudTalkApplication .sharedApplication!! .contentResolver.getType(uri)
1461+ val cursor = NextcloudTalkApplication .sharedApplication!! .contentResolver.query(uri, null , null , null , null )
1462+ cursor?.use {
1463+ val nameIndex = it.getColumnIndex(OpenableColumns .DISPLAY_NAME )
1464+ val sizeIndex = it.getColumnIndex(OpenableColumns .SIZE )
1465+ if (it.moveToFirst()) {
1466+ val name = if (nameIndex >= 0 ) it.getString(nameIndex).orEmpty() else uri.lastPathSegment.orEmpty()
1467+ val size = if (sizeIndex >= 0 ) it.getLong(sizeIndex) else 0L
1468+ return Triple (name, mimeType, size)
1469+ }
1470+ }
1471+ return Triple (uri.lastPathSegment.orEmpty(), mimeType, 0L )
1472+ }
1473+
1474+ private fun observeUploadProgress (workerId : UUID , referenceId : String ) {
1475+ WorkManager .getInstance(NextcloudTalkApplication .sharedApplication!! )
1476+ .getWorkInfoByIdLiveData(workerId)
1477+ .asFlow()
1478+ .onEach { workInfo ->
1479+ if (workInfo == null ) return @onEach
1480+ val progress = workInfo.progress.getInt(UploadAndShareFilesWorker .PROGRESS_KEY , - 1 )
1481+ if (progress >= 0 ) {
1482+ _uploadProgressMap .update { it + (referenceId to progress) }
1483+ }
1484+ if (workInfo.state.isFinished) {
1485+ _uploadProgressMap .update { it - referenceId }
1486+ uploadReferenceToUri.remove(referenceId)
1487+ }
1488+ }
1489+ .launchIn(viewModelScope)
1490+ }
1491+
14121492 fun postToRecordTouchObserver (float : Float ) {
14131493 _recordTouchObserver .postValue(float)
14141494 }
0 commit comments