Skip to content
837 changes: 837 additions & 0 deletions app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/22.json

Large diffs are not rendered by default.

21 changes: 17 additions & 4 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,17 @@ class ChatActivity :
}.collect()
}

this.lifecycleScope.launch {
chatViewModel.getUploadsFlow
.onEach {
val builder = StringBuilder()
it.forEach { item ->
builder.append("${item.fileName} ${item.progress}")
}
binding.fileUploadText.text = builder.toString()
}.collect()
}

chatViewModel.getRoomViewState.observe(this) { state ->
when (state) {
is ChatViewModel.GetRoomSuccessState -> {
Expand Down Expand Up @@ -3311,8 +3322,8 @@ class ChatActivity :
hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.THREADS)

val threadNotificationIcon = when (conversationThreadInfo?.attendee?.notificationLevel) {
1 -> R.drawable.outline_notifications_active_24
3 -> R.drawable.ic_baseline_notifications_off_24
NOTIFICATION_LEVEL_ALWAYS -> R.drawable.outline_notifications_active_24
NOTIFICATION_LEVEL_NEVER -> R.drawable.ic_baseline_notifications_off_24
else -> R.drawable.baseline_notifications_24
}
threadNotificationItem.icon = ContextCompat.getDrawable(context, threadNotificationIcon)
Expand Down Expand Up @@ -3418,7 +3429,7 @@ class ChatActivity :
subtitle = null,
icon = R.drawable.ic_baseline_notifications_off_24,
onClick = {
setThreadNotificationLevel(3)
setThreadNotificationLevel(NOTIFICATION_LEVEL_NEVER)
}
)
)
Expand Down Expand Up @@ -4089,7 +4100,7 @@ class ChatActivity :
displayName = currentConversation?.displayName ?: ""
)
showSnackBar(roomToken)
} catch (e: Exception) {
} catch (e: IOException) {
Log.w(TAG, "File corresponding to the uri does not exist $shareUri", e)
downloadFileToCache(message, false) {
uploadFile(
Expand Down Expand Up @@ -4594,6 +4605,8 @@ class ChatActivity :
private const val FIVE_MINUTES_IN_SECONDS: Long = 300
private const val ROOM_TYPE_ONE_TO_ONE = "1"
private const val ACTOR_TYPE = "users"
private const val NOTIFICATION_LEVEL_ALWAYS = 1
private const val NOTIFICATION_LEVEL_NEVER = 3
const val CONVERSATION_INTERNAL_ID = "CONVERSATION_INTERNAL_ID"
const val NO_OFFLINE_MESSAGES_FOUND = "NO_OFFLINE_MESSAGES_FOUND"
const val VOICE_MESSAGE_CONTINUOUS_BEFORE = -5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import android.os.Bundle
import com.nextcloud.talk.chat.data.io.LifecycleAwareManager
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.FileUploadModel
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
Expand All @@ -36,6 +37,11 @@ interface ChatMessageRepository : LifecycleAwareManager {

val lastReadMessageFlow: Flow<Int>

/**
* Stream of uploaded files in the conversation
*/
val uploadsFlow: Flow<List<FileUploadModel>>

/**
* Used for informing the user of the underlying processing behind offline support, [String] is the key
* which is handled in a switch statement in ChatActivity.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,8 @@ data class ChatMessage(
SINGLE_NC_GEOLOCATION_MESSAGE,
POLL_MESSAGE,
VOICE_MESSAGE,
DECK_CARD
DECK_CARD,
FILE_UPLOAD_MESSAGE
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.nextcloud.talk.chat.data.ChatMessageRepository
import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.data.database.dao.ChatBlocksDao
import com.nextcloud.talk.data.database.dao.ChatMessagesDao
import com.nextcloud.talk.data.database.dao.FileUploadsDao
import com.nextcloud.talk.data.database.mappers.asEntity
import com.nextcloud.talk.data.database.mappers.asModel
import com.nextcloud.talk.data.database.model.ChatBlockEntity
Expand All @@ -24,6 +25,7 @@ import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.extensions.toIntOrZero
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.FileUploadModel
import com.nextcloud.talk.models.json.chat.ChatMessageJson
import com.nextcloud.talk.models.json.chat.ChatOverall
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
Expand All @@ -42,10 +44,12 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.io.IOException
Expand All @@ -55,6 +59,7 @@ import javax.inject.Inject
class OfflineFirstChatRepository @Inject constructor(
private val chatDao: ChatMessagesDao,
private val chatBlocksDao: ChatBlocksDao,
private val fileUploadsDao: FileUploadsDao,
private val network: ChatNetworkDataSource,
private val networkMonitor: NetworkMonitor,
userProvider: CurrentUserProviderNew
Expand Down Expand Up @@ -111,6 +116,10 @@ class OfflineFirstChatRepository @Inject constructor(
private val _removeMessageFlow:
MutableSharedFlow<ChatMessage> = MutableSharedFlow()

override val uploadsFlow: Flow<List<FileUploadModel>>
get() = _uploadsFlow
private val _uploadsFlow: MutableSharedFlow<List<FileUploadModel>> = MutableSharedFlow()

private var newXChatLastCommonRead: Int? = null
private var itIsPaused = false
private lateinit var scope: CoroutineScope
Expand Down Expand Up @@ -196,6 +205,7 @@ class OfflineFirstChatRepository @Inject constructor(
}

handleMessagesFromDb(newestMessageIdFromDb)
handleUploadsFromDb()

initMessagePolling(newestMessageIdFromDb)
}
Expand Down Expand Up @@ -229,6 +239,27 @@ class OfflineFirstChatRepository @Inject constructor(
}
}

private fun handleUploadsFromDb() {
scope.launch {
fileUploadsDao.getFileUploadsForConversation(internalConversationId)
.onEach {
_uploadsFlow.emit(
it.map { item ->
FileUploadModel(
id = item.id,
fileName = item.fileName,
progress = item.progress
)
}
)
}
.catch {
Log.e(TAG, "Failed reading uploads")
}
.collect()
}
}

private suspend fun getCappedMessagesAmountOfChatBlock(messageId: Long): Int {
val chatBlock = getBlockOfMessage(messageId.toInt())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ class ChatViewModel @Inject constructor(
_getRoomViewState.value = GetRoomErrorState
}

val getUploadsFlow = chatRepository.uploadsFlow

val getGeneralUIFlow = chatRepository.generalUIFlow

sealed interface ViewState
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class OfflineFirstConversationsRepository @Inject constructor(
private val networkMonitor: NetworkMonitor,
private val currentUserProviderNew: CurrentUserProviderNew
) : OfflineConversationsRepository {

override val roomListFlow: Flow<List<ConversationModel>>
get() = _roomListFlow
private val _roomListFlow: MutableSharedFlow<List<ConversationModel>> = MutableSharedFlow()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package com.nextcloud.talk.dagger.modules
import com.nextcloud.talk.data.database.dao.ChatBlocksDao
import com.nextcloud.talk.data.database.dao.ChatMessagesDao
import com.nextcloud.talk.data.database.dao.ConversationsDao
import com.nextcloud.talk.data.database.dao.FileUploadsDao
import com.nextcloud.talk.data.source.local.TalkDatabase
import dagger.Module
import dagger.Provides
Expand All @@ -24,4 +25,7 @@ internal object DaosModule {

@Provides
fun providesChatBlocksDao(database: TalkDatabase): ChatBlocksDao = database.chatBlocksDao()

@Provides
fun provideFileUPloadDao(database: TalkDatabase): FileUploadsDao = database.fileUploadsDao()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

providesFileUploadsDao

}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import com.nextcloud.talk.conversationlist.data.network.RetrofitConversationsNet
import com.nextcloud.talk.data.database.dao.ChatBlocksDao
import com.nextcloud.talk.data.database.dao.ChatMessagesDao
import com.nextcloud.talk.data.database.dao.ConversationsDao
import com.nextcloud.talk.data.database.dao.FileUploadsDao
import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.source.local.TalkDatabase
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository
Expand Down Expand Up @@ -153,13 +154,15 @@ class RepositoryModule {
fun provideOfflineFirstChatRepository(
chatMessagesDao: ChatMessagesDao,
chatBlocksDao: ChatBlocksDao,
fileUploadsDao: FileUploadsDao,
dataSource: ChatNetworkDataSource,
networkMonitor: NetworkMonitor,
userProvider: CurrentUserProviderNew
): ChatMessageRepository =
OfflineFirstChatRepository(
chatMessagesDao,
chatBlocksDao,
fileUploadsDao,
dataSource,
networkMonitor,
userProvider
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 David Leibovych <[email protected]>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.data.database.dao

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.nextcloud.talk.data.database.model.FileUploadEntity
import kotlinx.coroutines.flow.Flow

@Dao
interface FileUploadsDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun createFileUpload(entity: FileUploadEntity): Long

@Query(
"""
SELECT *
FROM FileUploads
WHERE internalConversationId = :internalConversationId
ORDER BY timestamp DESC, id DESC
"""
)
fun getFileUploadsForConversation(internalConversationId: String): Flow<List<FileUploadEntity>>

@Query("UPDATE FileUploads SET progress = :progress WHERE id = :id")
fun updateProgress(id: Int, progress: Float)

@Query("UPDATE FileUploads SET status = 'STARTED' WHERE id = :id")
fun setStarted(id: Long)

@Query("UPDATE FileUploads SET status = 'COMPLETED' WHERE id = :id")
fun setCompleted(id: Long)

@Query("UPDATE FileUploads SET status = 'FAILED' WHERE id = :id")
fun setFailed(id: Long)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Your Name <[email protected]>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package com.nextcloud.talk.data.database.model

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey

@Entity(
tableName = "FileUploads",
foreignKeys = [
ForeignKey(
entity = ConversationEntity::class,
parentColumns = arrayOf("internalId"),
childColumns = arrayOf("internalConversationId"),
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
],
indices = [
Index(value = ["internalConversationId"])
]
)
data class FileUploadEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id") var id: Long = 0,
@ColumnInfo(name = "internalConversationId") val internalConversationId: String,
@ColumnInfo(name = "fileName") val fileName: String? = null,
@ColumnInfo(name = "progress") var progress: Float = 0f,
@ColumnInfo(name = "status") var status: String? = null,
@ColumnInfo(name = "hidden") var hidden: Boolean = false,
@ColumnInfo(name = "timestamp") var timestamp: Long = 0
)
Original file line number Diff line number Diff line change
Expand Up @@ -418,4 +418,31 @@ object Migrations {
Log.i("Migrations", "Something went wrong when adding column silent to table ChatMessages", e)
}
}

fun migrateToFileUploads(db: SupportSQLiteDatabase) {
try {
db.execSQL(
"CREATE TABLE IF NOT EXISTS FileUploads (" +
"id INTEGER NOT NULL, " +
"internalConversationId TEXT NOT NULL, " +
"fileName TEXT, " +
"progress REAL NOT NULL, " +
"status TEXT, " +
"hidden INTEGER NOT NULL, " +
"timestamp INTEGER NOT NULL, " +
"PRIMARY KEY (id), " +
"FOREIGN KEY (internalConversationId) " +
"REFERENCES Conversations(id) " +
"ON UPDATE CASCADE " +
"ON DELETE CASCADE" +
")"
)

db.execSQL(
"CREATE INDEX `index_FileUploads_internalConversationId` ON `FileUploads` (`internalConversationId`);"
)
} catch (e: SQLException) {
Log.i("Migrations", "Something went wrong while creating the FileUploads table", e)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.data.database.dao.ChatBlocksDao
import com.nextcloud.talk.data.database.dao.ChatMessagesDao
import com.nextcloud.talk.data.database.dao.ConversationsDao
import com.nextcloud.talk.data.database.dao.FileUploadsDao
import com.nextcloud.talk.data.database.model.ChatBlockEntity
import com.nextcloud.talk.data.database.model.ChatMessageEntity
import com.nextcloud.talk.data.database.model.ConversationEntity
import com.nextcloud.talk.data.database.model.FileUploadEntity
import com.nextcloud.talk.data.source.local.Migrations.AutoMigration16To17
import com.nextcloud.talk.data.source.local.converters.ArrayListConverter
import com.nextcloud.talk.data.source.local.converters.CapabilitiesConverter
Expand All @@ -46,14 +48,16 @@ import java.util.Locale
ArbitraryStorageEntity::class,
ConversationEntity::class,
ChatMessageEntity::class,
ChatBlockEntity::class
ChatBlockEntity::class,
FileUploadEntity::class
],
version = 21,
version = 22,
autoMigrations = [
AutoMigration(from = 9, to = 10),
AutoMigration(from = 16, to = 17, spec = AutoMigration16To17::class),
AutoMigration(from = 19, to = 20),
AutoMigration(from = 20, to = 21)
AutoMigration(from = 20, to = 21),
AutoMigration(from = 21, to = 22)
],
exportSchema = true
)
Expand All @@ -75,6 +79,7 @@ abstract class TalkDatabase : RoomDatabase() {
abstract fun conversationsDao(): ConversationsDao
abstract fun chatMessagesDao(): ChatMessagesDao
abstract fun chatBlocksDao(): ChatBlocksDao
abstract fun fileUploadsDao(): FileUploadsDao
abstract fun arbitraryStoragesDao(): ArbitraryStoragesDao

companion object {
Expand Down
Loading
Loading