From 9cb40d4eb67c4f1fc32397204d1ffb2e7674eaa1 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Wed, 6 Nov 2024 12:24:10 +0100 Subject: [PATCH 01/40] add referenceId for "normal" sending of chat message Signed-off-by: Marcel Hibbe --- .../13.json | 731 ++++++++++++++++++ .../java/com/nextcloud/talk/api/NcApi.java | 6 +- .../talk/chat/data/model/ChatMessage.kt | 57 +- .../data/network/ChatNetworkDataSource.kt | 7 +- .../chat/data/network/RetrofitChatNetwork.kt | 62 +- .../talk/chat/viewmodels/ChatViewModel.kt | 11 +- .../chat/viewmodels/MessageInputViewModel.kt | 19 +- .../database/mappers/ChatMessageMapUtils.kt | 9 +- .../data/database/model/ChatMessageEntity.kt | 2 +- .../talk/data/source/local/Migrations.kt | 18 + .../talk/data/source/local/TalkDatabase.kt | 10 +- .../talk/models/json/chat/ChatMessageJson.kt | 3 +- .../talk/receivers/DirectReplyReceiver.kt | 22 +- .../talk/utils/message/SendMessageUtils.kt | 23 + 14 files changed, 883 insertions(+), 97 deletions(-) create mode 100644 app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json create mode 100644 app/src/main/java/com/nextcloud/talk/utils/message/SendMessageUtils.kt diff --git a/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json new file mode 100644 index 000000000..fdeb82d1e --- /dev/null +++ b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json @@ -0,0 +1,731 @@ +{ + "formatVersion": 1, + "database": { + "version": 13, + "identityHash": "6986b68476bae871773348987eada812", + "entities": [ + { + "tableName": "User", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userId` TEXT, `username` TEXT, `baseUrl` TEXT, `token` TEXT, `displayName` TEXT, `pushConfigurationState` TEXT, `capabilities` TEXT, `serverVersion` TEXT DEFAULT '', `clientCertificate` TEXT, `externalSignalingServer` TEXT, `current` INTEGER NOT NULL, `scheduledForDeletion` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "userId", + "columnName": "userId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "baseUrl", + "columnName": "baseUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "pushConfigurationState", + "columnName": "pushConfigurationState", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "capabilities", + "columnName": "capabilities", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "serverVersion", + "columnName": "serverVersion", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "''" + }, + { + "fieldPath": "clientCertificate", + "columnName": "clientCertificate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "externalSignalingServer", + "columnName": "externalSignalingServer", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "current", + "columnName": "current", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "scheduledForDeletion", + "columnName": "scheduledForDeletion", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ArbitraryStorage", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`accountIdentifier` INTEGER NOT NULL, `key` TEXT NOT NULL, `object` TEXT, `value` TEXT, PRIMARY KEY(`accountIdentifier`, `key`))", + "fields": [ + { + "fieldPath": "accountIdentifier", + "columnName": "accountIdentifier", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "storageObject", + "columnName": "object", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "accountIdentifier", + "key" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Conversations", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `displayName` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `avatarVersion` TEXT NOT NULL, `callFlag` INTEGER NOT NULL, `callRecording` INTEGER NOT NULL, `callStartTime` INTEGER NOT NULL, `canDeleteConversation` INTEGER NOT NULL, `canLeaveConversation` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `description` TEXT NOT NULL, `hasCall` INTEGER NOT NULL, `hasPassword` INTEGER NOT NULL, `isCustomAvatar` INTEGER NOT NULL, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `lastMessage` TEXT, `lastPing` INTEGER NOT NULL, `lastReadMessage` INTEGER NOT NULL, `lobbyState` TEXT NOT NULL, `lobbyTimer` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `name` TEXT NOT NULL, `notificationCalls` INTEGER NOT NULL, `notificationLevel` TEXT NOT NULL, `objectType` TEXT NOT NULL, `participantType` TEXT NOT NULL, `permissions` INTEGER NOT NULL, `readOnly` TEXT NOT NULL, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, `sessionId` TEXT NOT NULL, `status` TEXT, `statusClearAt` INTEGER, `statusIcon` TEXT, `statusMessage` TEXT, `type` TEXT NOT NULL, `unreadMention` INTEGER NOT NULL, `unreadMentionDirect` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `hasArchived` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "internalId", + "columnName": "internalId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "actorId", + "columnName": "actorId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "actorType", + "columnName": "actorType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "avatarVersion", + "columnName": "avatarVersion", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "callFlag", + "columnName": "callFlag", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "callRecording", + "columnName": "callRecording", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "callStartTime", + "columnName": "callStartTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canDeleteConversation", + "columnName": "canDeleteConversation", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canLeaveConversation", + "columnName": "canLeaveConversation", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canStartCall", + "columnName": "canStartCall", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "hasCall", + "columnName": "hasCall", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasPassword", + "columnName": "hasPassword", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasCustomAvatar", + "columnName": "isCustomAvatar", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "favorite", + "columnName": "isFavorite", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastActivity", + "columnName": "lastActivity", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastCommonReadMessage", + "columnName": "lastCommonReadMessage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastMessage", + "columnName": "lastMessage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastPing", + "columnName": "lastPing", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastReadMessage", + "columnName": "lastReadMessage", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lobbyState", + "columnName": "lobbyState", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lobbyTimer", + "columnName": "lobbyTimer", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "messageExpiration", + "columnName": "messageExpiration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notificationCalls", + "columnName": "notificationCalls", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notificationLevel", + "columnName": "notificationLevel", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "objectType", + "columnName": "objectType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "participantType", + "columnName": "participantType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "permissions", + "columnName": "permissions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "conversationReadOnlyState", + "columnName": "readOnly", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "recordingConsentRequired", + "columnName": "recordingConsent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "remoteServer", + "columnName": "remoteServer", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "remoteToken", + "columnName": "remoteToken", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sessionId", + "columnName": "sessionId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "status", + "columnName": "status", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "statusClearAt", + "columnName": "statusClearAt", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "statusIcon", + "columnName": "statusIcon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "statusMessage", + "columnName": "statusMessage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unreadMention", + "columnName": "unreadMention", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadMentionDirect", + "columnName": "unreadMentionDirect", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unreadMessages", + "columnName": "unreadMessages", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasArchived", + "columnName": "hasArchived", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "internalId" + ] + }, + "indices": [ + { + "name": "index_Conversations_accountId", + "unique": false, + "columnNames": [ + "accountId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_Conversations_accountId` ON `${TABLE_NAME}` (`accountId`)" + } + ], + "foreignKeys": [ + { + "table": "User", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "accountId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "ChatMessages", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `systemMessage` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "internalId", + "columnName": "internalId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "internalConversationId", + "columnName": "internalConversationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "actorDisplayName", + "columnName": "actorDisplayName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "actorId", + "columnName": "actorId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "actorType", + "columnName": "actorType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "expirationTimestamp", + "columnName": "expirationTimestamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "replyable", + "columnName": "isReplyable", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastEditActorDisplayName", + "columnName": "lastEditActorDisplayName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastEditActorId", + "columnName": "lastEditActorId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastEditActorType", + "columnName": "lastEditActorType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastEditTimestamp", + "columnName": "lastEditTimestamp", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "renderMarkdown", + "columnName": "markdown", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "messageParameters", + "columnName": "messageParameters", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "messageType", + "columnName": "messageType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "parentMessageId", + "columnName": "parent", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "reactions", + "columnName": "reactions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reactionsSelf", + "columnName": "reactionsSelf", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "referenceId", + "columnName": "referenceId", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "systemMessageType", + "columnName": "systemMessage", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timestamp", + "columnName": "timestamp", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "internalId" + ] + }, + "indices": [ + { + "name": "index_ChatMessages_internalId", + "unique": true, + "columnNames": [ + "internalId" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_ChatMessages_internalId` ON `${TABLE_NAME}` (`internalId`)" + }, + { + "name": "index_ChatMessages_internalConversationId", + "unique": false, + "columnNames": [ + "internalConversationId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ChatMessages_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)" + } + ], + "foreignKeys": [ + { + "table": "Conversations", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "internalConversationId" + ], + "referencedColumns": [ + "internalId" + ] + } + ] + }, + { + "tableName": "ChatBlocks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `internalConversationId` TEXT NOT NULL, `accountId` INTEGER, `token` TEXT, `oldestMessageId` INTEGER NOT NULL, `newestMessageId` INTEGER NOT NULL, `hasHistory` INTEGER NOT NULL, FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "internalConversationId", + "columnName": "internalConversationId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "accountId", + "columnName": "accountId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "token", + "columnName": "token", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "oldestMessageId", + "columnName": "oldestMessageId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "newestMessageId", + "columnName": "newestMessageId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "hasHistory", + "columnName": "hasHistory", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_ChatBlocks_internalConversationId", + "unique": false, + "columnNames": [ + "internalConversationId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_ChatBlocks_internalConversationId` ON `${TABLE_NAME}` (`internalConversationId`)" + } + ], + "foreignKeys": [ + { + "table": "Conversations", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "internalConversationId" + ], + "referencedColumns": [ + "internalId" + ] + } + ] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6986b68476bae871773348987eada812')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index a404d973a..989ef2c1f 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -344,12 +344,14 @@ public interface NcApi { @FormUrlEncoded @POST - Observable sendChatMessage(@Header("Authorization") String authorization, + Observable sendChatMessage(@Header("Authorization") String authorization, @Url String url, @Field("message") CharSequence message, @Field("actorDisplayName") String actorDisplayName, @Field("replyTo") Integer replyTo, - @Field("silent") Boolean sendWithoutNotification); + @Field("silent") Boolean sendWithoutNotification, + @Field("referenceId") String referenceId + ); @FormUrlEncoded @PUT diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt index c2f14ffc3..37bc3b213 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt @@ -117,9 +117,12 @@ data class ChatMessage( var isTempMessage: Boolean = false, - var tempMessageId: Int = -1 + var tempMessageId: Int = -1, -) : MessageContentType, MessageContentType.Image { + var referenceId: String? = null + +) : MessageContentType, + MessageContentType.Image { var extractedUrlToPreview: String? = null @@ -240,8 +243,8 @@ data class ChatMessage( } } - fun getCalculateMessageType(): MessageType { - return if (!TextUtils.isEmpty(systemMessage)) { + fun getCalculateMessageType(): MessageType = + if (!TextUtils.isEmpty(systemMessage)) { MessageType.SYSTEM_MESSAGE } else if (isVoiceMessage) { MessageType.VOICE_MESSAGE @@ -256,19 +259,15 @@ data class ChatMessage( } else { MessageType.REGULAR_TEXT_MESSAGE } - } - override fun getId(): String { - return jsonMessageId.toString() - } + override fun getId(): String = jsonMessageId.toString() - override fun getText(): String { - return if (message != null) { + override fun getText(): String = + if (message != null) { getParsedMessage(message, messageParameters)!! } else { "" } - } fun getNullsafeActorDisplayName() = if (!TextUtils.isEmpty(actorDisplayName)) { @@ -277,22 +276,19 @@ data class ChatMessage( sharedApplication!!.getString(R.string.nc_guest) } - override fun getUser(): IUser { - return object : IUser { - override fun getId(): String { - return "$actorType/$actorId" - } + override fun getUser(): IUser = + object : IUser { + override fun getId(): String = "$actorType/$actorId" - override fun getName(): String { - return if (!TextUtils.isEmpty(actorDisplayName)) { + override fun getName(): String = + if (!TextUtils.isEmpty(actorDisplayName)) { actorDisplayName!! } else { sharedApplication!!.getString(R.string.nc_guest) } - } - override fun getAvatar(): String? { - return when { + override fun getAvatar(): String? = + when { activeUser == null -> { null } @@ -317,21 +313,14 @@ data class ChatMessage( ApiUtils.getUrlForGuestAvatar(activeUser!!.baseUrl!!, apiId, true) } } - } } - } - override fun getCreatedAt(): Date { - return Date(timestamp * MILLIES) - } + override fun getCreatedAt(): Date = Date(timestamp * MILLIES) - override fun getSystemMessage(): String { - return EnumSystemMessageTypeConverter().convertToString(systemMessageType) - } + override fun getSystemMessage(): String = EnumSystemMessageTypeConverter().convertToString(systemMessageType) - private fun isHashMapEntryEqualTo(map: HashMap, key: String, searchTerm: String): Boolean { - return map != null && MessageDigest.isEqual(map[key]!!.toByteArray(), searchTerm.toByteArray()) - } + private fun isHashMapEntryEqualTo(map: HashMap, key: String, searchTerm: String): Boolean = + map != null && MessageDigest.isEqual(map[key]!!.toByteArray(), searchTerm.toByteArray()) // needed a equals and hashcode function to fix detekt errors override fun equals(other: Any?): Boolean { @@ -340,9 +329,7 @@ data class ChatMessage( return false } - override fun hashCode(): Int { - return 0 - } + override fun hashCode(): Int = 0 val isVoiceMessage: Boolean get() = "voice-message" == messageType diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt index 81a6ec6c8..a4561b8ef 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt @@ -38,7 +38,7 @@ interface ChatNetworkDataSource { url: String, message: String, displayName: String - ): Observable // last two fields are false + ): Observable fun checkForNoteToSelf(credentials: String, url: String, includeStatus: Boolean): Observable fun shareLocationToNotes( @@ -56,8 +56,9 @@ interface ChatNetworkDataSource { message: CharSequence, displayName: String, replyTo: Int, - sendWithoutNotification: Boolean - ): Observable + sendWithoutNotification: Boolean, + referenceId: String + ): Observable fun pullChatMessages(credentials: String, url: String, fieldMap: HashMap): Observable> fun deleteChatMessage(credentials: String, url: String): Observable diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt index eafab6484..7b9aa5aa7 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt @@ -18,6 +18,7 @@ import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.reminder.Reminder import com.nextcloud.talk.models.json.userAbsence.UserAbsenceOverall import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.message.SendMessageUtils import io.reactivex.Observable import retrofit2.Response @@ -108,26 +109,24 @@ class RetrofitChatNetwork( url: String, message: String, displayName: String - ): Observable { - return ncApi.sendChatMessage( + ): Observable = + ncApi.sendChatMessage( credentials, url, message, displayName, null, - false + false, + SendMessageUtils().generateReferenceId() // TODO add temp message before with ref id.. ).map { it } - } override fun checkForNoteToSelf( credentials: String, url: String, includeStatus: Boolean - ): Observable { - return ncApi.getRooms(credentials, url, includeStatus).map { it } - } + ): Observable = ncApi.getRooms(credentials, url, includeStatus).map { it } override fun shareLocationToNotes( credentials: String, @@ -135,13 +134,12 @@ class RetrofitChatNetwork( objectType: String, objectId: String, metadata: String - ): Observable { - return ncApi.sendLocation(credentials, url, objectType, objectId, metadata).map { it } - } + ): Observable = ncApi.sendLocation(credentials, url, objectType, objectId, metadata).map { it } - override fun leaveRoom(credentials: String, url: String): Observable { - return ncApi.leaveRoom(credentials, url).map { it } - } + override fun leaveRoom(credentials: String, url: String): Observable = + ncApi.leaveRoom(credentials, url).map { + it + } override fun sendChatMessage( credentials: String, @@ -149,36 +147,42 @@ class RetrofitChatNetwork( message: CharSequence, displayName: String, replyTo: Int, - sendWithoutNotification: Boolean - ): Observable { - return ncApi.sendChatMessage(credentials, url, message, displayName, replyTo, sendWithoutNotification).map { + sendWithoutNotification: Boolean, + referenceId: String + ): Observable = + ncApi.sendChatMessage( + credentials, + url, + message, + displayName, + replyTo, + sendWithoutNotification, + referenceId + ).map { it } - } override fun pullChatMessages( credentials: String, url: String, fieldMap: HashMap - ): Observable> { - return ncApi.pullChatMessages(credentials, url, fieldMap).map { it } - } + ): Observable> = ncApi.pullChatMessages(credentials, url, fieldMap).map { it } - override fun deleteChatMessage(credentials: String, url: String): Observable { - return ncApi.deleteChatMessage(credentials, url).map { it } - } + override fun deleteChatMessage(credentials: String, url: String): Observable = + ncApi.deleteChatMessage(credentials, url).map { + it + } - override fun createRoom(credentials: String, url: String, map: Map): Observable { - return ncApi.createRoom(credentials, url, map).map { it } - } + override fun createRoom(credentials: String, url: String, map: Map): Observable = + ncApi.createRoom(credentials, url, map).map { + it + } override fun setChatReadMarker( credentials: String, url: String, previousMessageId: Int - ): Observable { - return ncApi.setChatReadMarker(credentials, url, previousMessageId).map { it } - } + ): Observable = ncApi.setChatReadMarker(credentials, url, previousMessageId).map { it } override fun editChatMessage(credentials: String, url: String, text: String): Observable { return ncApi.editChatMessage(credentials, url, text).map { it } diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt index 64b029ae6..58a2d0d35 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt @@ -63,7 +63,8 @@ class ChatViewModel @Inject constructor( private val mediaRecorderManager: MediaRecorderManager, private val audioFocusRequestManager: AudioFocusRequestManager, private val userProvider: CurrentUserProviderNew -) : ViewModel(), DefaultLifecycleObserver { +) : ViewModel(), + DefaultLifecycleObserver { enum class LifeCycleFlag { PAUSED, @@ -478,12 +479,12 @@ class ChatViewModel @Inject constructor( chatNetworkDataSource.shareToNotes(credentials, url, message, displayName) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { + ?.subscribe(object : Observer { override fun onSubscribe(d: Disposable) { disposableSet.add(d) } - override fun onNext(genericOverall: GenericOverall) { + override fun onNext(genericOverall: ChatOverallSingleMessage) { // unused atm } @@ -609,9 +610,7 @@ class ChatViewModel @Inject constructor( cachedFile.delete() } - fun getCurrentVoiceRecordFile(): String { - return mediaRecorderManager.currentVoiceRecordFile - } + fun getCurrentVoiceRecordFile(): String = mediaRecorderManager.currentVoiceRecordFile fun uploadFile(fileUri: String, room: String, displayName: String, metaData: String) { try { diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 54c73869d..191006d86 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -21,7 +21,7 @@ import com.nextcloud.talk.chat.data.io.MediaPlayerManager import com.nextcloud.talk.chat.data.model.ChatMessage import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage -import com.nextcloud.talk.models.json.generic.GenericOverall +import com.nextcloud.talk.utils.message.SendMessageUtils import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.commons.models.IMessage import io.reactivex.Observer @@ -39,7 +39,8 @@ class MessageInputViewModel @Inject constructor( private val mediaPlayerManager: MediaPlayerManager, private val audioFocusRequestManager: AudioFocusRequestManager, private val appPreferences: AppPreferences -) : ViewModel(), DefaultLifecycleObserver { +) : ViewModel(), + DefaultLifecycleObserver { enum class LifeCycleFlag { PAUSED, RESUMED, @@ -144,6 +145,11 @@ class MessageInputViewModel @Inject constructor( replyTo: Int, sendWithoutNotification: Boolean ) { + // TODO: add temporary message with ref id + + val referenceId = SendMessageUtils().generateReferenceId() + Log.d(TAG, "Random SHA-256 Hash: $referenceId") + if (isQueueing) { val tempID = System.currentTimeMillis().toInt() val qMsg = QueuedMessage(tempID, message, displayName, replyTo, sendWithoutNotification) @@ -161,10 +167,11 @@ class MessageInputViewModel @Inject constructor( message, displayName, replyTo, - sendWithoutNotification + sendWithoutNotification, + referenceId ).subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { + ?.subscribe(object : Observer { override fun onSubscribe(d: Disposable) { disposableSet.add(d) } @@ -177,7 +184,9 @@ class MessageInputViewModel @Inject constructor( // unused atm } - override fun onNext(t: GenericOverall) { + override fun onNext(t: ChatOverallSingleMessage) { + Log.d(TAG, "received ref id: " + (t.ocs?.data?.referenceId ?: "none")) + // TODO check ref id and replace temp message _sendChatMessageViewState.value = SendChatMessageSuccessState(message) } }) diff --git a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt index 30b856a6b..4ab3c4955 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt @@ -37,7 +37,8 @@ fun ChatMessageJson.asEntity(accountId: Long) = lastEditActorId = lastEditActorId, lastEditActorType = lastEditActorType, lastEditTimestamp = lastEditTimestamp, - deleted = deleted + deleted = deleted, + referenceId = referenceId ) fun ChatMessageEntity.asModel() = @@ -62,7 +63,8 @@ fun ChatMessageEntity.asModel() = lastEditActorId = lastEditActorId, lastEditActorType = lastEditActorType, lastEditTimestamp = lastEditTimestamp, - isDeleted = deleted + isDeleted = deleted, + referenceId = referenceId ) fun ChatMessageJson.asModel() = @@ -87,5 +89,6 @@ fun ChatMessageJson.asModel() = lastEditActorId = lastEditActorId, lastEditActorType = lastEditActorType, lastEditTimestamp = lastEditTimestamp, - isDeleted = deleted + isDeleted = deleted, + referenceId = referenceId ) diff --git a/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt index dbf1cce92..19ed015bd 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt @@ -62,8 +62,8 @@ data class ChatMessageEntity( @ColumnInfo(name = "parent") var parentMessageId: Long? = null, @ColumnInfo(name = "reactions") var reactions: LinkedHashMap? = null, @ColumnInfo(name = "reactionsSelf") var reactionsSelf: ArrayList? = null, + @ColumnInfo(name = "referenceId") var referenceId: String? = null, @ColumnInfo(name = "systemMessage") var systemMessageType: ChatMessage.SystemMessageType, @ColumnInfo(name = "timestamp") var timestamp: Long = 0 - // missing/not needed: referenceId // missing/not needed: silent ) diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt index 3e61f699d..8d10b94b3 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt @@ -48,6 +48,13 @@ object Migrations { } } + val MIGRATION_12_13 = object : Migration(12, 13) { + override fun migrate(db: SupportSQLiteDatabase) { + Log.i("Migrations", "Migrating 12 to 13") + addReferenceIdToChatMessages(db) + } + } + fun migrateToRoom(db: SupportSQLiteDatabase) { db.execSQL( "CREATE TABLE User_new (" + @@ -257,4 +264,15 @@ object Migrations { Log.i("Migrations", "hasArchived already exists") } } + + fun addReferenceIdToChatMessages(db: SupportSQLiteDatabase) { + try { + db.execSQL( + "ALTER TABLE ChatMessages " + + "ADD COLUMN referenceId TEXT;" + ) + } catch (e: SQLException) { + Log.i("Migrations", "Something went wrong when adding column referenceId to table ChatMessages") + } + } } diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt index 053ad4766..5c3656c76 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt @@ -49,7 +49,7 @@ import java.util.Locale ChatMessageEntity::class, ChatBlockEntity::class ], - version = 12, + version = 13, autoMigrations = [ AutoMigration(from = 9, to = 11) ], @@ -114,7 +114,8 @@ abstract class TalkDatabase : RoomDatabase() { Migrations.MIGRATION_7_8, Migrations.MIGRATION_8_9, Migrations.MIGRATION_10_11, - Migrations.MIGRATION_11_12 + Migrations.MIGRATION_11_12, + Migrations.MIGRATION_12_13 ) .allowMainThreadQueries() .addCallback( @@ -128,8 +129,8 @@ abstract class TalkDatabase : RoomDatabase() { .build() } - private fun getCipherMigrationHook(): SQLiteDatabaseHook { - return object : SQLiteDatabaseHook { + private fun getCipherMigrationHook(): SQLiteDatabaseHook = + object : SQLiteDatabaseHook { override fun preKey(database: SQLiteDatabase) { // unused atm } @@ -140,6 +141,5 @@ abstract class TalkDatabase : RoomDatabase() { Log.i(TAG, "DB cipher_migrate END") } } - } } } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt index 024e13fe6..60d039704 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt @@ -42,5 +42,6 @@ data class ChatMessageJson( @JsonField(name = ["lastEditActorId"]) var lastEditActorId: String? = null, @JsonField(name = ["lastEditActorType"]) var lastEditActorType: String? = null, @JsonField(name = ["lastEditTimestamp"]) var lastEditTimestamp: Long? = 0, - @JsonField(name = ["deleted"]) var deleted: Boolean = false + @JsonField(name = ["deleted"]) var deleted: Boolean = false, + @JsonField(name = ["referenceId"]) var referenceId: String? = null ) : Parcelable diff --git a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt index 09984c0c6..c70fe8b2f 100644 --- a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt +++ b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt @@ -23,13 +23,14 @@ import com.nextcloud.talk.R import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.data.user.model.User -import com.nextcloud.talk.models.json.generic.GenericOverall +import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.NotificationUtils import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SYSTEM_NOTIFICATION_ID +import com.nextcloud.talk.utils.message.SendMessageUtils import io.reactivex.Observer import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers @@ -71,24 +72,31 @@ class DirectReplyReceiver : BroadcastReceiver() { sendDirectReply() } - private fun getMessageText(intent: Intent): CharSequence? { - return RemoteInput.getResultsFromIntent(intent)?.getCharSequence(NotificationUtils.KEY_DIRECT_REPLY) - } + private fun getMessageText(intent: Intent): CharSequence? = + RemoteInput.getResultsFromIntent(intent)?.getCharSequence(NotificationUtils.KEY_DIRECT_REPLY) private fun sendDirectReply() { val credentials = ApiUtils.getCredentials(currentUser.username, currentUser.token) val apiVersion = ApiUtils.getChatApiVersion(currentUser.capabilities!!.spreedCapability!!, intArrayOf(1)) val url = ApiUtils.getUrlForChat(apiVersion, currentUser.baseUrl!!, roomToken!!) - ncApi.sendChatMessage(credentials, url, replyMessage, currentUser.displayName, null, false) + ncApi.sendChatMessage( + credentials, + url, + replyMessage, + currentUser.displayName, + null, + false, + SendMessageUtils().generateReferenceId() // TODO add temp chatMessage before with ref id... + ) ?.subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { + ?.subscribe(object : Observer { override fun onSubscribe(d: Disposable) { // unused atm } - override fun onNext(genericOverall: GenericOverall) { + override fun onNext(message: ChatOverallSingleMessage) { confirmReplySent() } diff --git a/app/src/main/java/com/nextcloud/talk/utils/message/SendMessageUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/message/SendMessageUtils.kt new file mode 100644 index 000000000..29b9700ea --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/utils/message/SendMessageUtils.kt @@ -0,0 +1,23 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2024 Marcel Hibbe + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.talk.utils.message + +import java.security.MessageDigest +import java.util.UUID + +class SendMessageUtils { + fun generateReferenceId(): String { + val randomString = UUID.randomUUID().toString() + val digest = MessageDigest.getInstance("SHA-256") + val hashBytes = digest.digest(randomString.toByteArray(Charsets.UTF_8)) + return hashBytes.joinToString("") { "%02x".format(it) } + } + + companion object { + private val TAG = SendMessageUtils::class.java.simpleName + } +} From 3597cf20853e0f1c3ef5683fbd79f4ab2234e043 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Wed, 6 Nov 2024 15:03:22 +0100 Subject: [PATCH 02/40] use repository in MessageInputViewModel instead datasource (as datasources should be only used in repositories) use coroutines instead RxJava for api calls triggered by MessageInputViewModel Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/api/NcApi.java | 6 -- .../com/nextcloud/talk/api/NcApiCoroutines.kt | 21 +++++ .../com/nextcloud/talk/chat/ChatActivity.kt | 25 +++-- .../talk/chat/data/ChatMessageRepository.kt | 13 +++ .../data/network/ChatNetworkDataSource.kt | 4 +- .../network/OfflineFirstChatRepository.kt | 46 ++++++++++ .../chat/data/network/RetrofitChatNetwork.kt | 10 +- .../chat/viewmodels/MessageInputViewModel.kt | 82 +++++++---------- .../OfflineFirstConversationsRepository.kt | 3 +- .../talk/dagger/modules/RepositoryModule.kt | 91 +++++++------------ 10 files changed, 162 insertions(+), 139 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApi.java b/app/src/main/java/com/nextcloud/talk/api/NcApi.java index 989ef2c1f..ca5d7afe5 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApi.java +++ b/app/src/main/java/com/nextcloud/talk/api/NcApi.java @@ -353,12 +353,6 @@ public interface NcApi { @Field("referenceId") String referenceId ); - @FormUrlEncoded - @PUT - Observable editChatMessage(@Header("Authorization") String authorization, - @Url String url, - @Field("message") String message); - @GET Observable> getSharedItems( @Header("Authorization") String authorization, diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt index 26e67f261..db6a00c55 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt +++ b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt @@ -8,6 +8,7 @@ package com.nextcloud.talk.api import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall +import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.models.json.conversations.RoomOverall import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.models.json.participants.AddParticipantOverall @@ -122,6 +123,26 @@ interface NcApiCoroutines { @DELETE suspend fun unarchiveConversation(@Header("Authorization") authorization: String, @Url url: String): GenericOverall + @FormUrlEncoded + @POST + suspend fun sendChatMessage( + @Header("Authorization") authorization: String, + @Url url: String, + @Field("message") message: CharSequence, + @Field("actorDisplayName") actorDisplayName: String, + @Field("replyTo") replyTo: Int, + @Field("silent") sendWithoutNotification: Boolean, + @Field("referenceId") referenceId: String + ): ChatOverallSingleMessage + + @FormUrlEncoded + @PUT + suspend fun editChatMessage( + @Header("Authorization") authorization: String, + @Url url: String, + @Field("message") message: String + ): ChatOverallSingleMessage + @FormUrlEncoded @POST suspend fun banActor( diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 5432675aa..98fd9c014 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -208,7 +208,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -import retrofit2.HttpException import java.io.File import java.io.IOException import java.net.HttpURLConnection @@ -813,18 +812,18 @@ class ChatActivity : } is MessageInputViewModel.SendChatMessageErrorState -> { - if (state.e is HttpException) { - val code = state.e.code() - if (code.toString().startsWith("2")) { - myFirstMessage = state.message - - if (binding.unreadMessagesPopup.isShown) { - binding.unreadMessagesPopup.visibility = View.GONE - } - - binding.messagesListView.smoothScrollToPosition(0) - } - } + // if (state.e is HttpException) { + // val code = state.e.code() + // if (code.toString().startsWith("2")) { + // myFirstMessage = state.message + // + // if (binding.unreadMessagesPopup.isShown) { + // binding.unreadMessagesPopup.visibility = View.GONE + // } + // + // binding.messagesListView.smoothScrollToPosition(0) + // } + // } } else -> {} diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt index 5cbf39efe..010e18d7c 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt @@ -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.json.chat.ChatOverallSingleMessage import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow @@ -75,4 +76,16 @@ interface ChatMessageRepository : LifecycleAwareManager { * Destroys unused resources. */ fun handleChatOnBackPress() + + suspend fun sendChatMessage( + credentials: String, + url: String, + message: CharSequence, + displayName: String, + replyTo: Int, + sendWithoutNotification: Boolean, + referenceId: String + ): Flow> + + suspend fun editChatMessage(credentials: String, url: String, text: String): Flow> } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt index a4561b8ef..f6e02e9d3 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt @@ -50,7 +50,7 @@ interface ChatNetworkDataSource { ): Observable fun leaveRoom(credentials: String, url: String): Observable - fun sendChatMessage( + suspend fun sendChatMessage( credentials: String, url: String, message: CharSequence, @@ -58,7 +58,7 @@ interface ChatNetworkDataSource { replyTo: Int, sendWithoutNotification: Boolean, referenceId: String - ): Observable + ): ChatOverallSingleMessage fun pullChatMessages(credentials: String, url: String, fieldMap: HashMap): Observable> fun deleteChatMessage(credentials: String, url: String): Observable diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 9a50d7c68..ad717408f 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -24,6 +24,7 @@ import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.models.json.chat.ChatMessageJson import com.nextcloud.talk.models.json.chat.ChatOverall +import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew import com.nextcloud.talk.utils.preferences.AppPreferences @@ -37,6 +38,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -752,6 +754,50 @@ class OfflineFirstChatRepository @Inject constructor( scope.cancel() } + override suspend fun sendChatMessage( + credentials: String, + url: String, + message: CharSequence, + displayName: String, + replyTo: Int, + sendWithoutNotification: Boolean, + referenceId: String + ): Flow> = + flow { + try { + val response = network.sendChatMessage( + credentials, + url, + message, + displayName, + replyTo, + sendWithoutNotification, + referenceId + ) + emit(Result.success(response)) + } catch (e: Exception) { + emit(Result.failure(e)) + } + } + + override suspend fun editChatMessage( + credentials: String, + url: String, + text: String + ): Flow> = + flow { + try { + val response = network.editChatMessage( + credentials, + url, + text + ) + emit(Result.success(response)) + } catch (e: Exception) { + emit(Result.failure(e)) + } + } + companion object { val TAG = OfflineFirstChatRepository::class.simpleName private const val HTTP_CODE_OK: Int = 200 diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt index 7b9aa5aa7..2af4b9f14 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt @@ -141,7 +141,7 @@ class RetrofitChatNetwork( it } - override fun sendChatMessage( + override suspend fun sendChatMessage( credentials: String, url: String, message: CharSequence, @@ -149,8 +149,8 @@ class RetrofitChatNetwork( replyTo: Int, sendWithoutNotification: Boolean, referenceId: String - ): Observable = - ncApi.sendChatMessage( + ): ChatOverallSingleMessage = + ncApiCoroutines.sendChatMessage( credentials, url, message, @@ -158,9 +158,7 @@ class RetrofitChatNetwork( replyTo, sendWithoutNotification, referenceId - ).map { - it - } + ) override fun pullChatMessages( credentials: String, diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 191006d86..4f2331547 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -15,6 +15,8 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import com.nextcloud.talk.chat.data.ChatMessageRepository import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager import com.nextcloud.talk.chat.data.io.AudioRecorderManager import com.nextcloud.talk.chat.data.io.MediaPlayerManager @@ -24,17 +26,15 @@ import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.utils.message.SendMessageUtils import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.commons.models.IMessage -import io.reactivex.Observer -import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import java.lang.Thread.sleep import javax.inject.Inject class MessageInputViewModel @Inject constructor( - private val chatNetworkDataSource: ChatNetworkDataSource, + private val chatRepository: ChatMessageRepository, private val audioRecorderManager: AudioRecorderManager, private val mediaPlayerManager: MediaPlayerManager, private val audioFocusRequestManager: AudioFocusRequestManager, @@ -107,7 +107,7 @@ class MessageInputViewModel @Inject constructor( sealed interface ViewState object SendChatMessageStartState : ViewState class SendChatMessageSuccessState(val message: CharSequence) : ViewState - class SendChatMessageErrorState(val e: Throwable, val message: CharSequence) : ViewState + class SendChatMessageErrorState(val message: CharSequence) : ViewState private val _sendChatMessageViewState: MutableLiveData = MutableLiveData(SendChatMessageStartState) val sendChatMessageViewState: LiveData get() = _sendChatMessageViewState @@ -161,59 +161,41 @@ class MessageInputViewModel @Inject constructor( return } - chatNetworkDataSource.sendChatMessage( - credentials, - url, - message, - displayName, - replyTo, - sendWithoutNotification, - referenceId - ).subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - disposableSet.add(d) - } + viewModelScope.launch { + chatRepository.sendChatMessage( + credentials, + url, + message, + displayName, + replyTo, + sendWithoutNotification, + referenceId + ).collect { result -> + if (result.isSuccess) { + Log.d(TAG, "received ref id: " + (result.getOrNull()?.ocs?.data?.referenceId ?: "none")) - override fun onError(e: Throwable) { - _sendChatMessageViewState.value = SendChatMessageErrorState(e, message) - } - - override fun onComplete() { - // unused atm - } - - override fun onNext(t: ChatOverallSingleMessage) { - Log.d(TAG, "received ref id: " + (t.ocs?.data?.referenceId ?: "none")) - // TODO check ref id and replace temp message _sendChatMessageViewState.value = SendChatMessageSuccessState(message) + } else { + _sendChatMessageViewState.value = SendChatMessageErrorState(message) } - }) + } + } } fun editChatMessage(credentials: String, url: String, text: String) { - chatNetworkDataSource.editChatMessage(credentials, url, text) - .subscribeOn(Schedulers.io()) - ?.observeOn(AndroidSchedulers.mainThread()) - ?.subscribe(object : Observer { - override fun onSubscribe(d: Disposable) { - disposableSet.add(d) - } - - override fun onError(e: Throwable) { - Log.e(TAG, "failed to edit message", e) + viewModelScope.launch { + chatRepository.editChatMessage( + credentials, + url, + text + ).collect { result -> + if (result.isSuccess) { + _editMessageViewState.value = EditMessageSuccessState(result.getOrNull()!!) + } else { _editMessageViewState.value = EditMessageErrorState } - - override fun onComplete() { - // unused atm - } - - override fun onNext(messageEdited: ChatOverallSingleMessage) { - _editMessageViewState.value = EditMessageSuccessState(messageEdited) - } - }) + } + } } fun reply(message: IMessage?) { diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt index f2d30a83b..8a68e366a 100644 --- a/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt @@ -10,7 +10,6 @@ package com.nextcloud.talk.conversationlist.data.network import android.util.Log import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource -import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository import com.nextcloud.talk.data.database.dao.ConversationsDao import com.nextcloud.talk.data.database.mappers.asEntity @@ -107,7 +106,7 @@ class OfflineFirstConversationsRepository @Inject constructor( var conversationsFromSync: List? = null if (!monitor.isOnline.first()) { - Log.d(OfflineFirstChatRepository.TAG, "Device is offline, can't load conversations from server") + Log.d(TAG, "Device is offline, can't load conversations from server") return null } diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt index 0efa202ce..b81a1604b 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt @@ -74,87 +74,66 @@ class RepositoryModule { ncApi: NcApi, ncApiCoroutines: NcApiCoroutines, userProvider: CurrentUserProviderNew - ): ConversationsRepository { - return ConversationsRepositoryImpl(ncApi, ncApiCoroutines, userProvider) - } + ): ConversationsRepository = ConversationsRepositoryImpl(ncApi, ncApiCoroutines, userProvider) @Provides - fun provideSharedItemsRepository(ncApi: NcApi, dateUtils: DateUtils): SharedItemsRepository { - return SharedItemsRepositoryImpl(ncApi, dateUtils) - } + fun provideSharedItemsRepository(ncApi: NcApi, dateUtils: DateUtils): SharedItemsRepository = + SharedItemsRepositoryImpl(ncApi, dateUtils) @Provides - fun provideUnifiedSearchRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): UnifiedSearchRepository { - return UnifiedSearchRepositoryImpl(ncApi, userProvider) - } + fun provideUnifiedSearchRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): UnifiedSearchRepository = + UnifiedSearchRepositoryImpl(ncApi, userProvider) @Provides - fun provideDialogPollRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): PollRepository { - return PollRepositoryImpl(ncApi, userProvider) - } + fun provideDialogPollRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): PollRepository = + PollRepositoryImpl(ncApi, userProvider) @Provides fun provideRemoteFileBrowserItemsRepository( okHttpClient: OkHttpClient, userProvider: CurrentUserProviderNew - ): RemoteFileBrowserItemsRepository { - return RemoteFileBrowserItemsRepositoryImpl(okHttpClient, userProvider) - } + ): RemoteFileBrowserItemsRepository = RemoteFileBrowserItemsRepositoryImpl(okHttpClient, userProvider) @Provides - fun provideUsersRepository(database: TalkDatabase): UsersRepository { - return UsersRepositoryImpl(database.usersDao()) - } + fun provideUsersRepository(database: TalkDatabase): UsersRepository = UsersRepositoryImpl(database.usersDao()) @Provides - fun provideArbitraryStoragesRepository(database: TalkDatabase): ArbitraryStoragesRepository { - return ArbitraryStoragesRepositoryImpl(database.arbitraryStoragesDao()) - } + fun provideArbitraryStoragesRepository(database: TalkDatabase): ArbitraryStoragesRepository = + ArbitraryStoragesRepositoryImpl(database.arbitraryStoragesDao()) @Provides fun provideReactionsRepository( ncApi: NcApi, userProvider: CurrentUserProviderNew, dao: ChatMessagesDao - ): ReactionsRepository { - return ReactionsRepositoryImpl(ncApi, userProvider, dao) - } + ): ReactionsRepository = ReactionsRepositoryImpl(ncApi, userProvider, dao) @Provides - fun provideCallRecordingRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): CallRecordingRepository { - return CallRecordingRepositoryImpl(ncApi, userProvider) - } + fun provideCallRecordingRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): CallRecordingRepository = + CallRecordingRepositoryImpl(ncApi, userProvider) @Provides fun provideRequestAssistanceRepository( ncApi: NcApi, userProvider: CurrentUserProviderNew - ): RequestAssistanceRepository { - return RequestAssistanceRepositoryImpl(ncApi, userProvider) - } + ): RequestAssistanceRepository = RequestAssistanceRepositoryImpl(ncApi, userProvider) @Provides fun provideOpenConversationsRepository( ncApi: NcApi, userProvider: CurrentUserProviderNew - ): OpenConversationsRepository { - return OpenConversationsRepositoryImpl(ncApi, userProvider) - } + ): OpenConversationsRepository = OpenConversationsRepositoryImpl(ncApi, userProvider) @Provides - fun translateRepository(ncApi: NcApi): TranslateRepository { - return TranslateRepositoryImpl(ncApi) - } + fun translateRepository(ncApi: NcApi): TranslateRepository = TranslateRepositoryImpl(ncApi) @Provides - fun provideChatNetworkDataSource(ncApi: NcApi, ncApiCoroutines: NcApiCoroutines): ChatNetworkDataSource { - return RetrofitChatNetwork(ncApi, ncApiCoroutines) - } + fun provideChatNetworkDataSource(ncApi: NcApi, ncApiCoroutines: NcApiCoroutines): ChatNetworkDataSource = + RetrofitChatNetwork(ncApi, ncApiCoroutines) @Provides - fun provideConversationsNetworkDataSource(ncApi: NcApi): ConversationsNetworkDataSource { - return RetrofitConversationsNetwork(ncApi) - } + fun provideConversationsNetworkDataSource(ncApi: NcApi): ConversationsNetworkDataSource = + RetrofitConversationsNetwork(ncApi) @Provides fun provideConversationInfoEditRepository( @@ -166,14 +145,11 @@ class RepositoryModule { } @Provides - fun provideConversationRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): ConversationRepository { - return ConversationRepositoryImpl(ncApi, userProvider) - } + fun provideConversationRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): ConversationRepository = + ConversationRepositoryImpl(ncApi, userProvider) @Provides - fun provideInvitationsRepository(ncApi: NcApi): InvitationsRepository { - return InvitationsRepositoryImpl(ncApi) - } + fun provideInvitationsRepository(ncApi: NcApi): InvitationsRepository = InvitationsRepositoryImpl(ncApi) @Provides fun provideOfflineFirstChatRepository( @@ -183,8 +159,8 @@ class RepositoryModule { appPreferences: AppPreferences, networkMonitor: NetworkMonitor, userProvider: CurrentUserProviderNew - ): ChatMessageRepository { - return OfflineFirstChatRepository( + ): ChatMessageRepository = + OfflineFirstChatRepository( chatMessagesDao, chatBlocksDao, dataSource, @@ -192,7 +168,6 @@ class RepositoryModule { networkMonitor, userProvider ) - } @Provides fun provideOfflineFirstConversationsRepository( @@ -201,26 +176,22 @@ class RepositoryModule { chatNetworkDataSource: ChatNetworkDataSource, networkMonitor: NetworkMonitor, currentUserProviderNew: CurrentUserProviderNew - ): OfflineConversationsRepository { - return OfflineFirstConversationsRepository( + ): OfflineConversationsRepository = + OfflineFirstConversationsRepository( dao, dataSource, chatNetworkDataSource, networkMonitor, currentUserProviderNew ) - } @Provides - fun provideContactsRepository(ncApiCoroutines: NcApiCoroutines, userManager: UserManager): ContactsRepository { - return ContactsRepositoryImpl(ncApiCoroutines, userManager) - } + fun provideContactsRepository(ncApiCoroutines: NcApiCoroutines, userManager: UserManager): ContactsRepository = + ContactsRepositoryImpl(ncApiCoroutines, userManager) @Provides fun provideConversationCreationRepository( ncApiCoroutines: NcApiCoroutines, userManager: UserManager - ): ConversationCreationRepository { - return ConversationCreationRepositoryImpl(ncApiCoroutines, userManager) - } + ): ConversationCreationRepository = ConversationCreationRepositoryImpl(ncApiCoroutines, userManager) } From c33bd997a40dea2e3c7554146c90c6b8b1e2660e Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 21 Nov 2024 22:39:57 +0100 Subject: [PATCH 03/40] fixes after some wrong merge after merge conflict Signed-off-by: Marcel Hibbe --- .../nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt index 2af4b9f14..33211a1af 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt @@ -182,9 +182,8 @@ class RetrofitChatNetwork( previousMessageId: Int ): Observable = ncApi.setChatReadMarker(credentials, url, previousMessageId).map { it } - override fun editChatMessage(credentials: String, url: String, text: String): Observable { - return ncApi.editChatMessage(credentials, url, text).map { it } - } + override suspend fun editChatMessage(credentials: String, url: String, text: String): ChatOverallSingleMessage = + ncApiCoroutines.editChatMessage(credentials, url, text) override suspend fun getOutOfOfficeStatusForUser( credentials: String, From 80d2e75b6070237008447a0f20a654127657d2f3 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 22 Nov 2024 12:49:09 +0100 Subject: [PATCH 04/40] WIP add temporary message Signed-off-by: Marcel Hibbe --- .../talk/chat/data/ChatMessageRepository.kt | 7 ++ .../network/OfflineFirstChatRepository.kt | 69 +++++++++++++++++++ .../chat/viewmodels/MessageInputViewModel.kt | 16 +++++ 3 files changed, 92 insertions(+) diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt index 010e18d7c..26e08174e 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt @@ -87,5 +87,12 @@ interface ChatMessageRepository : LifecycleAwareManager { referenceId: String ): Flow> + suspend fun addTemporaryMessage( + message: CharSequence, + displayName: String, + replyTo: Int, + referenceId: String + ): Flow> + suspend fun editChatMessage(credentials: String, url: String, text: String): Flow> } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index ad717408f..c1b96e61d 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -798,6 +798,75 @@ class OfflineFirstChatRepository @Inject constructor( } } + override suspend fun addTemporaryMessage( + message: CharSequence, + displayName: String, + replyTo: Int, + referenceId: String + ): Flow> = + flow { + try { + val tempChatMessageEntity = createChatMessageEntity(internalConversationId, message.toString()) + // accessing internalConversationId creates UninitializedPropertyException because ChatViewModel and + // MessageInputViewModel use different instances of ChatRepository for now + + + chatDao.upsertChatMessage(tempChatMessageEntity) + + val tempChatMessageModel = tempChatMessageEntity.asModel() + + // emit(Result.success(response)) + + val triple = Triple(false, false, listOf(tempChatMessageModel)) + _messageFlow.emit(triple) + + } catch (e: Exception) { + Log.e(TAG, "Something went wrong when adding temporary message", e) + emit(Result.failure(e)) + } + } + + private fun createChatMessageEntity(internalConversationId: String, message: String): ChatMessageEntity { + // val id = chatMessageCounter++ + + val emoji1 = "\uD83D\uDE00" // 😀 + val emoji2 = "\uD83D\uDE1C" // 😜 + val reactions = LinkedHashMap() + reactions[emoji1] = 3 + reactions[emoji2] = 4 + + val reactionsSelf = ArrayList() + reactionsSelf.add(emoji1) + + val entity = ChatMessageEntity( + internalId = internalConversationId + "_temp1", + internalConversationId = internalConversationId, + id = 111111111, + message = message, + reactions = reactions, + reactionsSelf = reactionsSelf, + deleted = false, + token = "", + actorId = "", + actorType = "", + accountId = 1, + messageParameters = null, + messageType = "", + parentMessageId = null, + systemMessageType = ChatMessage.SystemMessageType.DUMMY, + replyable = false, + timestamp = 0, + expirationTimestamp = 0, + actorDisplayName = "", + lastEditActorType = null, + lastEditTimestamp = null, + renderMarkdown = true, + lastEditActorId = "", + lastEditActorDisplayName = "" + ) + return entity + } + companion object { val TAG = OfflineFirstChatRepository::class.simpleName private const val HTTP_CODE_OK: Int = 200 diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 4f2331547..be006f425 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -150,6 +150,22 @@ class MessageInputViewModel @Inject constructor( val referenceId = SendMessageUtils().generateReferenceId() Log.d(TAG, "Random SHA-256 Hash: $referenceId") + viewModelScope.launch { + chatRepository.addTemporaryMessage( + message, + displayName, + replyTo, + referenceId + ).collect { result -> + if (result.isSuccess) { + Log.d(TAG, "bbbb") + } else { + Log.d(TAG, "xxxx") + } + } + } + + if (isQueueing) { val tempID = System.currentTimeMillis().toInt() val qMsg = QueuedMessage(tempID, message, displayName, replyTo, sendWithoutNotification) From 048551e9fb46b98c6cd181ac4ed0104482300d01 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Wed, 4 Dec 2024 10:30:51 +0100 Subject: [PATCH 05/40] temp message is added (values are still wrong) Signed-off-by: Marcel Hibbe --- .../talk/chat/data/ChatMessageRepository.kt | 4 ++-- .../network/OfflineFirstChatRepository.kt | 22 ++++++++++++------- .../chat/viewmodels/MessageInputViewModel.kt | 8 ++++--- .../talk/dagger/modules/RepositoryModule.kt | 2 ++ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt index 26e08174e..218c3b658 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt @@ -85,14 +85,14 @@ interface ChatMessageRepository : LifecycleAwareManager { replyTo: Int, sendWithoutNotification: Boolean, referenceId: String - ): Flow> + ): Flow> suspend fun addTemporaryMessage( message: CharSequence, displayName: String, replyTo: Int, referenceId: String - ): Flow> + ): Flow> suspend fun editChatMessage(credentials: String, url: String, text: String): Flow> } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index c1b96e61d..891cc7a61 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -762,7 +762,7 @@ class OfflineFirstChatRepository @Inject constructor( replyTo: Int, sendWithoutNotification: Boolean, referenceId: String - ): Flow> = + ): Flow> = flow { try { val response = network.sendChatMessage( @@ -774,8 +774,12 @@ class OfflineFirstChatRepository @Inject constructor( sendWithoutNotification, referenceId ) - emit(Result.success(response)) + + val chatMessageModel = response.ocs?.data?.asModel() + + emit(Result.success(chatMessageModel)) } catch (e: Exception) { + Log.e(TAG, "Error when sending message", e) emit(Result.failure(e)) } } @@ -803,7 +807,7 @@ class OfflineFirstChatRepository @Inject constructor( displayName: String, replyTo: Int, referenceId: String - ): Flow> = + ): Flow> = flow { try { val tempChatMessageEntity = createChatMessageEntity(internalConversationId, message.toString()) @@ -815,11 +819,13 @@ class OfflineFirstChatRepository @Inject constructor( val tempChatMessageModel = tempChatMessageEntity.asModel() - // emit(Result.success(response)) + emit(Result.success(tempChatMessageModel)) - val triple = Triple(false, false, listOf(tempChatMessageModel)) + val triple = Triple(true, false, listOf(tempChatMessageModel)) _messageFlow.emit(triple) + // emit() + } catch (e: Exception) { Log.e(TAG, "Something went wrong when adding temporary message", e) emit(Result.failure(e)) @@ -855,11 +861,11 @@ class OfflineFirstChatRepository @Inject constructor( parentMessageId = null, systemMessageType = ChatMessage.SystemMessageType.DUMMY, replyable = false, - timestamp = 0, + timestamp = System.currentTimeMillis(), expirationTimestamp = 0, - actorDisplayName = "", + actorDisplayName = "test", lastEditActorType = null, - lastEditTimestamp = null, + lastEditTimestamp = 0L, renderMarkdown = true, lastEditActorId = "", lastEditActorDisplayName = "" diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index be006f425..750d6fca0 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -158,9 +158,11 @@ class MessageInputViewModel @Inject constructor( referenceId ).collect { result -> if (result.isSuccess) { - Log.d(TAG, "bbbb") + Log.d(TAG, "temp message ref id: " + (result.getOrNull()?.referenceId ?: "none")) + + _sendChatMessageViewState.value = SendChatMessageSuccessState(message) } else { - Log.d(TAG, "xxxx") + _sendChatMessageViewState.value = SendChatMessageErrorState(message) } } } @@ -188,7 +190,7 @@ class MessageInputViewModel @Inject constructor( referenceId ).collect { result -> if (result.isSuccess) { - Log.d(TAG, "received ref id: " + (result.getOrNull()?.ocs?.data?.referenceId ?: "none")) + Log.d(TAG, "received ref id: " + (result.getOrNull()?.referenceId ?: "none")) _sendChatMessageViewState.value = SendChatMessageSuccessState(message) } else { diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt index b81a1604b..32c4eff9e 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt @@ -65,6 +65,7 @@ import com.nextcloud.talk.utils.preferences.AppPreferences import dagger.Module import dagger.Provides import okhttp3.OkHttpClient +import javax.inject.Singleton @Module class RepositoryModule { @@ -151,6 +152,7 @@ class RepositoryModule { @Provides fun provideInvitationsRepository(ncApi: NcApi): InvitationsRepository = InvitationsRepositoryImpl(ncApi) + @Singleton @Provides fun provideOfflineFirstChatRepository( chatMessagesDao: ChatMessagesDao, From 0e682ed8943270407e32775419fed0929725ae61 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Wed, 4 Dec 2024 15:46:54 +0100 Subject: [PATCH 06/40] WIP temp messages are replaced when same refId received from sever Signed-off-by: Marcel Hibbe --- .../13.json | 18 +++- .../com/nextcloud/talk/chat/ChatActivity.kt | 10 ++ .../talk/chat/data/ChatMessageRepository.kt | 2 + .../network/OfflineFirstChatRepository.kt | 98 ++++++++++++------- .../talk/chat/viewmodels/ChatViewModel.kt | 6 ++ .../chat/viewmodels/MessageInputViewModel.kt | 8 +- .../talk/dagger/modules/RepositoryModule.kt | 1 - .../talk/data/database/dao/ChatMessagesDao.kt | 22 +++++ .../data/database/model/ChatMessageEntity.kt | 4 +- .../talk/data/source/local/TalkDatabase.kt | 2 +- 10 files changed, 130 insertions(+), 41 deletions(-) diff --git a/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json index fdeb82d1e..3b4330bb9 100644 --- a/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json +++ b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 13, - "identityHash": "6986b68476bae871773348987eada812", + "identityHash": "ec1e16b220080592a488165e493b4f89", "entities": [ { "tableName": "User", @@ -450,7 +450,7 @@ }, { "tableName": "ChatMessages", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `systemMessage` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `systemMessage` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `isTemporary` INTEGER NOT NULL, `sendingFailed` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )", "fields": [ { "fieldPath": "internalId", @@ -601,6 +601,18 @@ "columnName": "timestamp", "affinity": "INTEGER", "notNull": true + }, + { + "fieldPath": "isTemporary", + "columnName": "isTemporary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sendingFailed", + "columnName": "sendingFailed", + "affinity": "INTEGER", + "notNull": true } ], "primaryKey": { @@ -725,7 +737,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6986b68476bae871773348987eada812')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ec1e16b220080592a488165e493b4f89')" ] } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 98fd9c014..6feb5fd34 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -451,6 +451,7 @@ class ChatActivity : chatViewModel = ViewModelProvider(this, viewModelFactory)[ChatViewModel::class.java] messageInputViewModel = ViewModelProvider(this, viewModelFactory)[MessageInputViewModel::class.java] + messageInputViewModel.setData(chatViewModel.getChatRepository()) this.lifecycleScope.launch { delay(DELAY_TO_SHOW_PROGRESS_BAR) @@ -936,6 +937,15 @@ class ChatActivity : .collect() } + this.lifecycleScope.launch { + chatViewModel.getRemoveMessageFlow + .onEach { + adapter!!.delete(it) + adapter!!.notifyDataSetChanged() + } + .collect() + } + this.lifecycleScope.launch { chatViewModel.getUpdateMessageFlow .onEach { diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt index 218c3b658..976f3db94 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt @@ -42,6 +42,8 @@ interface ChatMessageRepository : LifecycleAwareManager { */ val generalUIFlow: Flow + val removeMessageFlow: Flow + fun setData(conversationModel: ConversationModel, credentials: String, urlForChatting: String) fun loadInitialMessages(withNetworkParams: Bundle): Job diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 891cc7a61..fc5114b5d 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -73,8 +73,7 @@ class OfflineFirstChatRepository @Inject constructor( > > = MutableSharedFlow() - override val updateMessageFlow: - Flow + override val updateMessageFlow: Flow get() = _updateMessageFlow private val _updateMessageFlow: @@ -87,8 +86,7 @@ class OfflineFirstChatRepository @Inject constructor( private val _lastCommonReadFlow: MutableSharedFlow = MutableSharedFlow() - override val lastReadMessageFlow: - Flow + override val lastReadMessageFlow: Flow get() = _lastReadMessageFlow private val _lastReadMessageFlow: @@ -99,6 +97,12 @@ class OfflineFirstChatRepository @Inject constructor( private val _generalUIFlow: MutableSharedFlow = MutableSharedFlow() + override val removeMessageFlow: Flow + get() = _removeMessageFlow + + private val _removeMessageFlow: + MutableSharedFlow = MutableSharedFlow() + private var newXChatLastCommonRead: Int? = null private var itIsPaused = false private val scope = CoroutineScope(Dispatchers.IO) @@ -174,6 +178,9 @@ class OfflineFirstChatRepository @Inject constructor( if (newestMessageIdFromDb.toInt() != 0) { val limit = getCappedMessagesAmountOfChatBlock(newestMessageIdFromDb) + // TODO: somewhere here also handle temp messages. updateUiMessages(chatMessages, showUnreadMessagesMarker) + + showMessagesBeforeAndEqual( internalConversationId, newestMessageIdFromDb, @@ -295,8 +302,7 @@ class OfflineFirstChatRepository @Inject constructor( val weHaveMessagesFromOurself = chatMessages.any { it.actorId == currentUser.userId } showUnreadMessagesMarker = showUnreadMessagesMarker && !weHaveMessagesFromOurself - val triple = Triple(true, showUnreadMessagesMarker, chatMessages) - _messageFlow.emit(triple) + updateUiMessages(chatMessages, showUnreadMessagesMarker) } else { Log.d(TAG, "resultsFromSync are null or empty") } @@ -319,6 +325,33 @@ class OfflineFirstChatRepository @Inject constructor( } } + private suspend fun updateUiMessages(chatMessages : List, showUnreadMessagesMarker: Boolean) { + val oldTempMessages = chatDao.getTempMessagesForConversation(internalConversationId) + .first() + .map(ChatMessageEntity::asModel) + + oldTempMessages.forEach { _removeMessageFlow.emit(it) } + + val tripleChatMessages = Triple(true, showUnreadMessagesMarker, chatMessages) + _messageFlow.emit(tripleChatMessages) + + + val chatMessagesReferenceIds = chatMessages.mapTo(HashSet(chatMessages.size)) { it.referenceId } + val tempChatMessagesThatCanBeReplaced = oldTempMessages.filter { it.referenceId in chatMessagesReferenceIds } + + chatDao.deleteTempChatMessages( + internalConversationId, + tempChatMessagesThatCanBeReplaced.map { it.referenceId!! } + ) + + val remainingTempMessages = chatDao.getTempMessagesForConversation(internalConversationId) + .first() + .map(ChatMessageEntity::asModel) + + val triple = Triple(true, false, remainingTempMessages) + _messageFlow.emit(triple) + } + private suspend fun hasToLoadPreviousMessagesFromServer(beforeMessageId: Long): Boolean { val loadFromServer: Boolean @@ -810,7 +843,11 @@ class OfflineFirstChatRepository @Inject constructor( ): Flow> = flow { try { - val tempChatMessageEntity = createChatMessageEntity(internalConversationId, message.toString()) + val tempChatMessageEntity = createChatMessageEntity( + internalConversationId, + message.toString(), + referenceId + ) // accessing internalConversationId creates UninitializedPropertyException because ChatViewModel and // MessageInputViewModel use different instances of ChatRepository for now @@ -832,43 +869,35 @@ class OfflineFirstChatRepository @Inject constructor( } } - private fun createChatMessageEntity(internalConversationId: String, message: String): ChatMessageEntity { - // val id = chatMessageCounter++ + private fun createChatMessageEntity( + internalConversationId: String, + message: String, + referenceId: String + ): ChatMessageEntity { - val emoji1 = "\uD83D\uDE00" // 😀 - val emoji2 = "\uD83D\uDE1C" // 😜 - val reactions = LinkedHashMap() - reactions[emoji1] = 3 - reactions[emoji2] = 4 - - val reactionsSelf = ArrayList() - reactionsSelf.add(emoji1) + val currentTimeMillies = System.currentTimeMillis() val entity = ChatMessageEntity( - internalId = internalConversationId + "_temp1", + internalId = internalConversationId + "@_temp_" + currentTimeMillies, internalConversationId = internalConversationId, - id = 111111111, - message = message, - reactions = reactions, - reactionsSelf = reactionsSelf, + id = currentTimeMillies, + message = message + " (temp)", deleted = false, - token = "", - actorId = "", - actorType = "", - accountId = 1, + token = conversationModel.token, + actorId = currentUser.userId!!, + actorType = "users", + accountId = currentUser.id!!, messageParameters = null, - messageType = "", + messageType = "comment", parentMessageId = null, systemMessageType = ChatMessage.SystemMessageType.DUMMY, replyable = false, - timestamp = System.currentTimeMillis(), + timestamp = System.currentTimeMillis() / MILLIES, expirationTimestamp = 0, - actorDisplayName = "test", - lastEditActorType = null, - lastEditTimestamp = 0L, - renderMarkdown = true, - lastEditActorId = "", - lastEditActorDisplayName = "" + actorDisplayName = currentUser.displayName!!, + referenceId = referenceId, + isTemporary = true, + sendingFailed = false ) return entity } @@ -881,5 +910,6 @@ class OfflineFirstChatRepository @Inject constructor( private const val HALF_SECOND = 500L private const val DELAY_TO_ENSURE_MESSAGES_ARE_ADDED: Long = 100 private const val DEFAULT_MESSAGES_LIMIT = 100 + private const val MILLIES = 1000 } } diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt index 58a2d0d35..27f367556 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt @@ -75,6 +75,10 @@ class ChatViewModel @Inject constructor( lateinit var currentLifeCycleFlag: LifeCycleFlag val disposableSet = mutableSetOf() + fun getChatRepository(): ChatMessageRepository { + return chatRepository + } + override fun onResume(owner: LifecycleOwner) { super.onResume(owner) currentLifeCycleFlag = LifeCycleFlag.RESUMED @@ -132,6 +136,8 @@ class ChatViewModel @Inject constructor( _chatMessageViewState.value = ChatMessageErrorState } + val getRemoveMessageFlow = chatRepository.removeMessageFlow + val getUpdateMessageFlow = chatRepository.updateMessageFlow val getLastCommonReadFlow = chatRepository.lastCommonReadFlow diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 750d6fca0..0ca79ffad 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -34,21 +34,27 @@ import java.lang.Thread.sleep import javax.inject.Inject class MessageInputViewModel @Inject constructor( - private val chatRepository: ChatMessageRepository, private val audioRecorderManager: AudioRecorderManager, private val mediaPlayerManager: MediaPlayerManager, private val audioFocusRequestManager: AudioFocusRequestManager, private val appPreferences: AppPreferences ) : ViewModel(), DefaultLifecycleObserver { + enum class LifeCycleFlag { PAUSED, RESUMED, STOPPED } + + lateinit var chatRepository: ChatMessageRepository lateinit var currentLifeCycleFlag: LifeCycleFlag val disposableSet = mutableSetOf() + fun setData(chatMessageRepository: ChatMessageRepository){ + chatRepository = chatMessageRepository + } + data class QueuedMessage( val id: Int, var message: CharSequence? = null, diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt index 32c4eff9e..afa64efa5 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt @@ -152,7 +152,6 @@ class RepositoryModule { @Provides fun provideInvitationsRepository(ncApi: NcApi): InvitationsRepository = InvitationsRepositoryImpl(ncApi) - @Singleton @Provides fun provideOfflineFirstChatRepository( chatMessagesDao: ChatMessagesDao, diff --git a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt index 6fbf61ca1..6357ce236 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt @@ -22,6 +22,7 @@ interface ChatMessagesDao { SELECT MAX(id) as max_items FROM ChatMessages WHERE internalConversationId = :internalConversationId + AND isTemporary = 0 """ ) fun getNewestMessageId(internalConversationId: String): Long @@ -36,6 +37,17 @@ interface ChatMessagesDao { ) fun getMessagesForConversation(internalConversationId: String): Flow> + @Query( + """ + SELECT * + FROM ChatMessages + WHERE internalConversationId = :internalConversationId + AND isTemporary = 1 + ORDER BY timestamp DESC, id DESC + """ + ) + fun getTempMessagesForConversation(internalConversationId: String): Flow> + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun upsertChatMessages(chatMessages: List) @@ -59,6 +71,16 @@ interface ChatMessagesDao { ) fun deleteChatMessages(messageIds: List) + @Query( + value = """ + DELETE FROM ChatMessages + WHERE internalConversationId = :internalConversationId + AND referenceId in (:referenceIds) + AND isTemporary = 1 + """ + ) + fun deleteTempChatMessages(internalConversationId: String, referenceIds: List) + @Update fun updateChatMessage(message: ChatMessageEntity) diff --git a/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt index 19ed015bd..1b46d5e89 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt @@ -64,6 +64,8 @@ data class ChatMessageEntity( @ColumnInfo(name = "reactionsSelf") var reactionsSelf: ArrayList? = null, @ColumnInfo(name = "referenceId") var referenceId: String? = null, @ColumnInfo(name = "systemMessage") var systemMessageType: ChatMessage.SystemMessageType, - @ColumnInfo(name = "timestamp") var timestamp: Long = 0 + @ColumnInfo(name = "timestamp") var timestamp: Long = 0, + @ColumnInfo(name = "isTemporary") var isTemporary: Boolean = false, + @ColumnInfo(name = "sendingFailed") var sendingFailed: Boolean = false, // missing/not needed: silent ) diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt index 5c3656c76..8fa20e9d3 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt @@ -108,7 +108,7 @@ abstract class TalkDatabase : RoomDatabase() { return Room .databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName) // comment out openHelperFactory to view the database entries in Android Studio for debugging - .openHelperFactory(factory) + // .openHelperFactory(factory) .addMigrations( Migrations.MIGRATION_6_8, Migrations.MIGRATION_7_8, From a78c9e1c08da3bab8a5593a0bf3505dc3588e2bf Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 9 Dec 2024 18:55:35 +0100 Subject: [PATCH 07/40] update temp messages also for initial pull of messages Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 11 ++- .../network/OfflineFirstChatRepository.kt | 74 +++++++++---------- .../chat/viewmodels/MessageInputViewModel.kt | 3 - .../talk/data/database/dao/ChatMessagesDao.kt | 4 + 4 files changed, 47 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 6feb5fd34..d7ca12def 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -940,8 +940,7 @@ class ChatActivity : this.lifecycleScope.launch { chatViewModel.getRemoveMessageFlow .onEach { - adapter!!.delete(it) - adapter!!.notifyDataSetChanged() + removeMessageById(it.id) } .collect() } @@ -1180,9 +1179,15 @@ class ChatActivity : } private fun removeUnreadMessagesMarker() { - val index = adapter?.getMessagePositionById(UNREAD_MESSAGES_MARKER_ID.toString()) + removeMessageById(UNREAD_MESSAGES_MARKER_ID.toString()) + } + + // do not use adapter.deleteById() as it seems to contain a bug! Use this method instead! + private fun removeMessageById(idToDelete: String) { + val index = adapter?.getMessagePositionById(idToDelete) if (index != null && index != -1) { adapter?.items?.removeAt(index) + adapter?.notifyItemRemoved(index) } } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index fc5114b5d..1f8001fd0 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -178,14 +178,19 @@ class OfflineFirstChatRepository @Inject constructor( if (newestMessageIdFromDb.toInt() != 0) { val limit = getCappedMessagesAmountOfChatBlock(newestMessageIdFromDb) - // TODO: somewhere here also handle temp messages. updateUiMessages(chatMessages, showUnreadMessagesMarker) - - showMessagesBeforeAndEqual( - internalConversationId, + val list = getMessagesBeforeAndEqual( newestMessageIdFromDb, + internalConversationId, limit ) + if (list.isNotEmpty()) { + updateUiMessages( + chatMessages = list, + lookIntoFuture = false, + showUnreadMessagesMarker = false + ) + } // delay is a dirty workaround to make sure messages are added to adapter on initial load before dealing // with them (otherwise there is a race condition). @@ -302,7 +307,11 @@ class OfflineFirstChatRepository @Inject constructor( val weHaveMessagesFromOurself = chatMessages.any { it.actorId == currentUser.userId } showUnreadMessagesMarker = showUnreadMessagesMarker && !weHaveMessagesFromOurself - updateUiMessages(chatMessages, showUnreadMessagesMarker) + updateUiMessages( + chatMessages = chatMessages, + lookIntoFuture = true, + showUnreadMessagesMarker = showUnreadMessagesMarker + ) } else { Log.d(TAG, "resultsFromSync are null or empty") } @@ -325,25 +334,30 @@ class OfflineFirstChatRepository @Inject constructor( } } - private suspend fun updateUiMessages(chatMessages : List, showUnreadMessagesMarker: Boolean) { + private suspend fun updateUiMessages( + chatMessages : List, + lookIntoFuture: Boolean, + showUnreadMessagesMarker: Boolean + ) { + // remove all temp messages from UI val oldTempMessages = chatDao.getTempMessagesForConversation(internalConversationId) .first() .map(ChatMessageEntity::asModel) - oldTempMessages.forEach { _removeMessageFlow.emit(it) } - val tripleChatMessages = Triple(true, showUnreadMessagesMarker, chatMessages) + // add new messages to UI + val tripleChatMessages = Triple(lookIntoFuture, showUnreadMessagesMarker, chatMessages) _messageFlow.emit(tripleChatMessages) - + // remove temp messages from DB that are now found in the new messages val chatMessagesReferenceIds = chatMessages.mapTo(HashSet(chatMessages.size)) { it.referenceId } val tempChatMessagesThatCanBeReplaced = oldTempMessages.filter { it.referenceId in chatMessagesReferenceIds } - chatDao.deleteTempChatMessages( internalConversationId, tempChatMessagesThatCanBeReplaced.map { it.referenceId!! } ) + // add the remaining temp messages to UI again val remainingTempMessages = chatDao.getTempMessagesForConversation(internalConversationId) .first() .map(ChatMessageEntity::asModel) @@ -719,31 +733,19 @@ class OfflineFirstChatRepository @Inject constructor( } } - private suspend fun showMessagesBeforeAndEqual(internalConversationId: String, messageId: Long, limit: Int) { - suspend fun getMessagesBeforeAndEqual( - messageId: Long, - internalConversationId: String, - messageLimit: Int - ): List = - chatDao.getMessagesForConversationBeforeAndEqual( - internalConversationId, - messageId, - messageLimit - ).map { - it.map(ChatMessageEntity::asModel) - }.first() - - val list = getMessagesBeforeAndEqual( - messageId, + suspend fun getMessagesBeforeAndEqual( + messageId: Long, + internalConversationId: String, + messageLimit: Int + ): List = + chatDao.getMessagesForConversationBeforeAndEqual( internalConversationId, - limit - ) + messageId, + messageLimit + ).map { + it.map(ChatMessageEntity::asModel) + }.first() - if (list.isNotEmpty()) { - val triple = Triple(false, false, list) - _messageFlow.emit(triple) - } - } private suspend fun showMessagesBefore(internalConversationId: String, messageId: Long, limit: Int) { suspend fun getMessagesBefore( @@ -848,9 +850,6 @@ class OfflineFirstChatRepository @Inject constructor( message.toString(), referenceId ) - // accessing internalConversationId creates UninitializedPropertyException because ChatViewModel and - // MessageInputViewModel use different instances of ChatRepository for now - chatDao.upsertChatMessage(tempChatMessageEntity) @@ -860,9 +859,6 @@ class OfflineFirstChatRepository @Inject constructor( val triple = Triple(true, false, listOf(tempChatMessageModel)) _messageFlow.emit(triple) - - // emit() - } catch (e: Exception) { Log.e(TAG, "Something went wrong when adding temporary message", e) emit(Result.failure(e)) diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 0ca79ffad..c00e89546 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -151,8 +151,6 @@ class MessageInputViewModel @Inject constructor( replyTo: Int, sendWithoutNotification: Boolean ) { - // TODO: add temporary message with ref id - val referenceId = SendMessageUtils().generateReferenceId() Log.d(TAG, "Random SHA-256 Hash: $referenceId") @@ -173,7 +171,6 @@ class MessageInputViewModel @Inject constructor( } } - if (isQueueing) { val tempID = System.currentTimeMillis().toInt() val qMsg = QueuedMessage(tempID, message, displayName, replyTo, sendWithoutNotification) diff --git a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt index 6357ce236..a15cdbe1c 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt @@ -99,6 +99,7 @@ interface ChatMessagesDao { SELECT * FROM ChatMessages WHERE internalConversationId = :internalConversationId AND id >= :messageId + AND isTemporary = 0 ORDER BY timestamp ASC, id ASC """ ) @@ -109,6 +110,7 @@ interface ChatMessagesDao { SELECT * FROM ChatMessages WHERE internalConversationId = :internalConversationId + AND isTemporary = 0 AND id < :messageId ORDER BY timestamp DESC, id DESC LIMIT :limit @@ -125,6 +127,7 @@ interface ChatMessagesDao { SELECT * FROM ChatMessages WHERE internalConversationId = :internalConversationId + AND isTemporary = 0 AND id <= :messageId ORDER BY timestamp DESC, id DESC LIMIT :limit @@ -141,6 +144,7 @@ interface ChatMessagesDao { SELECT COUNT(*) FROM ChatMessages WHERE internalConversationId = :internalConversationId + AND isTemporary = 0 AND id BETWEEN :newestMessageId AND :oldestMessageId """ ) From e1c1574d6c6be23e0c8c958c19b653df2f2413e4 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Tue, 10 Dec 2024 14:35:56 +0100 Subject: [PATCH 08/40] show x when sending failed Signed-off-by: Marcel Hibbe --- .../OutcomingTextMessageViewHolder.kt | 44 ++++++++++++------- .../com/nextcloud/talk/chat/ChatActivity.kt | 14 ++++-- .../talk/chat/data/model/ChatMessage.kt | 8 ++-- .../network/OfflineFirstChatRepository.kt | 23 +++++++--- .../talk/data/database/dao/ChatMessagesDao.kt | 12 +++++ .../database/mappers/ChatMessageMapUtils.kt | 16 ++++++- .../talk/models/json/chat/ReadStatus.kt | 4 +- .../item_custom_outcoming_text_message.xml | 26 +++++++++++ 8 files changed, 115 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index b056ec0d8..804d2d093 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -114,7 +114,19 @@ class OutcomingTextMessageViewHolder(itemView: View) : binding.messageQuote.quotedChatMessageView.visibility = View.GONE } - setReadStatus(message.readStatus) + + when (message.readStatus) { + ReadStatus.READ -> updateReadStatus(R.drawable.ic_check_all, context.resources?.getString(R.string.nc_message_read)) + ReadStatus.SENT -> updateReadStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent)) + ReadStatus.SENDING -> updateSendingStatus() + ReadStatus.FAILED -> updateReadStatus( + R.drawable.ic_baseline_close_24, + "failed" + ) + else -> null + } + + itemView.setTag(R.string.replyable_message_view_tag, message.replyable) @@ -129,29 +141,27 @@ class OutcomingTextMessageViewHolder(itemView: View) : ) } - private fun setReadStatus(readStatus: Enum) { - val readStatusDrawableInt = when (readStatus) { - ReadStatus.READ -> R.drawable.ic_check_all - ReadStatus.SENT -> R.drawable.ic_check - else -> null - } - - val readStatusContentDescriptionString = when (readStatus) { - ReadStatus.READ -> context.resources?.getString(R.string.nc_message_read) - ReadStatus.SENT -> context.resources?.getString(R.string.nc_message_sent) - else -> null - } - - readStatusDrawableInt?.let { drawableInt -> + private fun updateReadStatus(readStatusDrawableInt: Int, description: String?) { + binding.sendingProgress.visibility = View.GONE + binding.checkMark.visibility = View.VISIBLE + readStatusDrawableInt.let { drawableInt -> ResourcesCompat.getDrawable(context.resources, drawableInt, null)?.let { binding.checkMark.setImageDrawable(it) viewThemeUtils.talk.themeMessageCheckMark(binding.checkMark) } } - - binding.checkMark.contentDescription = readStatusContentDescriptionString + binding.checkMark.contentDescription = description } + private fun updateSendingStatus() { + binding.sendingProgress.visibility = View.VISIBLE + binding.checkMark.visibility = View.GONE + + viewThemeUtils.material.colorProgressBar(binding.sendingProgress) + } + + + private fun longClickOnReaction(chatMessage: ChatMessage) { commonMessageInterface.onLongClickReactions(chatMessage) } diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index d7ca12def..e786832bc 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -587,7 +587,7 @@ class ChatActivity : list.forEachIndexed { _, qMsg -> val temporaryChatMessage = ChatMessage() temporaryChatMessage.jsonMessageId = TEMPORARY_MESSAGE_ID_INT - temporaryChatMessage.actorId = "-3" + temporaryChatMessage.actorId = TEMPORARY_MESSAGE_ID_STRING temporaryChatMessage.timestamp = System.currentTimeMillis() / ONE_SECOND_IN_MILLIS temporaryChatMessage.message = qMsg.message.toString() temporaryChatMessage.tempMessageId = qMsg.id @@ -813,6 +813,8 @@ class ChatActivity : } is MessageInputViewModel.SendChatMessageErrorState -> { + binding.messagesListView.smoothScrollToPosition(0) + // if (state.e is HttpException) { // val code = state.e.code() // if (code.toString().startsWith("2")) { @@ -2933,7 +2935,11 @@ class ChatActivity : if (message.item is ChatMessage) { val chatMessage = message.item as ChatMessage - if (chatMessage.jsonMessageId <= xChatLastCommonRead) { + if (chatMessage.sendingFailed) { + chatMessage.readStatus = ReadStatus.FAILED + } else if (chatMessage.isTempMessage) { + chatMessage.readStatus = ReadStatus.SENDING + } else if (chatMessage.jsonMessageId <= xChatLastCommonRead) { chatMessage.readStatus = ReadStatus.READ } else { chatMessage.readStatus = ReadStatus.SENT @@ -3439,7 +3445,7 @@ class ChatActivity : val message = iMessage as ChatMessage if (hasVisibleItems(message) && !isSystemMessage(message) && - message.id != "-3" + message.id != TEMPORARY_MESSAGE_ID_STRING ) { MessageActionsDialog( this, @@ -3863,7 +3869,7 @@ class ChatActivity : CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage) CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == UNREAD_MESSAGES_MARKER_ID.toString() CONTENT_TYPE_CALL_STARTED -> message.id == "-2" - CONTENT_TYPE_TEMP -> message.id == "-3" + CONTENT_TYPE_TEMP -> message.id == TEMPORARY_MESSAGE_ID_STRING CONTENT_TYPE_DECK_CARD -> message.isDeckCard() else -> false diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt index 37bc3b213..0e50ef18a 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt @@ -115,11 +115,13 @@ data class ChatMessage( var openWhenDownloaded: Boolean = true, - var isTempMessage: Boolean = false, + var isTempMessage: Boolean = false, // TODO: replace logic from message drafts with logic from temp message sending - var tempMessageId: Int = -1, + var tempMessageId: Int = -1, // TODO: replace logic from message drafts with logic from temp message sending - var referenceId: String? = null + var referenceId: String? = null, + + var sendingFailed: Boolean = true ) : MessageContentType, MessageContentType.Image { diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 1f8001fd0..7db771a2b 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -186,7 +186,7 @@ class OfflineFirstChatRepository @Inject constructor( ) if (list.isNotEmpty()) { updateUiMessages( - chatMessages = list, + receivedChatMessages = list, lookIntoFuture = false, showUnreadMessagesMarker = false ) @@ -308,7 +308,7 @@ class OfflineFirstChatRepository @Inject constructor( showUnreadMessagesMarker = showUnreadMessagesMarker && !weHaveMessagesFromOurself updateUiMessages( - chatMessages = chatMessages, + receivedChatMessages = chatMessages, lookIntoFuture = true, showUnreadMessagesMarker = showUnreadMessagesMarker ) @@ -335,7 +335,7 @@ class OfflineFirstChatRepository @Inject constructor( } private suspend fun updateUiMessages( - chatMessages : List, + receivedChatMessages : List, lookIntoFuture: Boolean, showUnreadMessagesMarker: Boolean ) { @@ -346,11 +346,11 @@ class OfflineFirstChatRepository @Inject constructor( oldTempMessages.forEach { _removeMessageFlow.emit(it) } // add new messages to UI - val tripleChatMessages = Triple(lookIntoFuture, showUnreadMessagesMarker, chatMessages) + val tripleChatMessages = Triple(lookIntoFuture, showUnreadMessagesMarker, receivedChatMessages) _messageFlow.emit(tripleChatMessages) // remove temp messages from DB that are now found in the new messages - val chatMessagesReferenceIds = chatMessages.mapTo(HashSet(chatMessages.size)) { it.referenceId } + val chatMessagesReferenceIds = receivedChatMessages.mapTo(HashSet(receivedChatMessages.size)) { it.referenceId } val tempChatMessagesThatCanBeReplaced = oldTempMessages.filter { it.referenceId in chatMessagesReferenceIds } chatDao.deleteTempChatMessages( internalConversationId, @@ -815,6 +815,17 @@ class OfflineFirstChatRepository @Inject constructor( emit(Result.success(chatMessageModel)) } catch (e: Exception) { Log.e(TAG, "Error when sending message", e) + + val failedMessage = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first() + failedMessage.sendingFailed = true + chatDao.updateChatMessage(failedMessage) + + val failedMessageModel = failedMessage.asModel() + _removeMessageFlow.emit(failedMessageModel) + + val tripleChatMessages = Triple(true, false, listOf(failedMessageModel)) + _messageFlow.emit(tripleChatMessages) + emit(Result.failure(e)) } } @@ -877,7 +888,7 @@ class OfflineFirstChatRepository @Inject constructor( internalId = internalConversationId + "@_temp_" + currentTimeMillies, internalConversationId = internalConversationId, id = currentTimeMillies, - message = message + " (temp)", + message = message, deleted = false, token = conversationModel.token, actorId = currentUser.userId!!, diff --git a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt index a15cdbe1c..b3179c149 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt @@ -48,6 +48,18 @@ interface ChatMessagesDao { ) fun getTempMessagesForConversation(internalConversationId: String): Flow> + @Query( + """ + SELECT * + FROM ChatMessages + WHERE internalConversationId = :internalConversationId + AND referenceId = :referenceId + AND isTemporary = 1 + ORDER BY timestamp DESC, id DESC + """ + ) + fun getTempMessageForConversation(internalConversationId: String, referenceId: String): Flow + @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun upsertChatMessages(chatMessages: List) diff --git a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt index 4ab3c4955..1392a2920 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt @@ -10,6 +10,7 @@ package com.nextcloud.talk.data.database.mappers import com.nextcloud.talk.models.json.chat.ChatMessageJson import com.nextcloud.talk.data.database.model.ChatMessageEntity import com.nextcloud.talk.chat.data.model.ChatMessage +import com.nextcloud.talk.models.json.chat.ReadStatus fun ChatMessageJson.asEntity(accountId: Long) = ChatMessageEntity( @@ -64,9 +65,22 @@ fun ChatMessageEntity.asModel() = lastEditActorType = lastEditActorType, lastEditTimestamp = lastEditTimestamp, isDeleted = deleted, - referenceId = referenceId + referenceId = referenceId, + isTempMessage = isTemporary, + sendingFailed = sendingFailed, + readStatus = setStatus(isTemporary, sendingFailed) ) +fun setStatus(isTemporary: Boolean, sendingFailed: Boolean): ReadStatus { + return if (sendingFailed) { + ReadStatus.FAILED + } else if (isTemporary) { + ReadStatus.SENDING + } else { + ReadStatus.NONE + } +} + fun ChatMessageJson.asModel() = ChatMessage( jsonMessageId = id.toInt(), diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ReadStatus.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ReadStatus.kt index 40a1e283c..1441dd521 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ReadStatus.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ReadStatus.kt @@ -9,5 +9,7 @@ package com.nextcloud.talk.models.json.chat enum class ReadStatus { NONE, SENT, - READ + READ, + SENDING, + FAILED } diff --git a/app/src/main/res/layout/item_custom_outcoming_text_message.xml b/app/src/main/res/layout/item_custom_outcoming_text_message.xml index 4d0d2d452..7c93c5d2a 100644 --- a/app/src/main/res/layout/item_custom_outcoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_outcoming_text_message.xml @@ -84,6 +84,32 @@ app:layout_alignSelf="center" app:tint="@color/high_emphasis_text" /> + + + + From 3f5f2f024a1e402a9eae280981f326131827665e Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Wed, 11 Dec 2024 14:41:26 +0100 Subject: [PATCH 09/40] works okay (no resend logic yet, offline message mode not reworked) Signed-off-by: Marcel Hibbe --- .../OutcomingTextMessageViewHolder.kt | 38 ++++++++----- .../messages/TemporaryMessageViewHolder.kt | 9 ++++ .../com/nextcloud/talk/chat/ChatActivity.kt | 8 +-- .../data/database/model/ChatMessageEntity.kt | 4 +- .../main/res/drawable/baseline_replay_24.xml | 5 ++ .../drawable/baseline_report_problem_24.xml | 5 ++ .../item_custom_outcoming_text_message.xml | 53 +++++++++++++++++-- .../res/layout/item_temporary_message.xml | 18 +++++-- 8 files changed, 112 insertions(+), 28 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_replay_24.xml create mode 100644 app/src/main/res/drawable/baseline_report_problem_24.xml diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 804d2d093..6d995febd 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -14,6 +14,7 @@ import android.util.Log import android.util.TypedValue import android.view.View import androidx.core.content.res.ResourcesCompat +import androidx.lifecycle.lifecycleScope import autodagger.AutoInjector import coil.load import com.google.android.flexbox.FlexboxLayout @@ -23,6 +24,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.data.model.ChatMessage +import com.nextcloud.talk.data.network.NetworkMonitor import com.nextcloud.talk.databinding.ItemCustomOutcomingTextMessageBinding import com.nextcloud.talk.models.json.chat.ReadStatus import com.nextcloud.talk.ui.theme.ViewThemeUtils @@ -58,6 +60,9 @@ class OutcomingTextMessageViewHolder(itemView: View) : @Inject lateinit var dateUtils: DateUtils + @Inject + lateinit var networkMonitor: NetworkMonitor + lateinit var commonMessageInterface: CommonMessageInterface override fun onBind(message: ChatMessage) { @@ -115,18 +120,25 @@ class OutcomingTextMessageViewHolder(itemView: View) : } - when (message.readStatus) { - ReadStatus.READ -> updateReadStatus(R.drawable.ic_check_all, context.resources?.getString(R.string.nc_message_read)) - ReadStatus.SENT -> updateReadStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent)) - ReadStatus.SENDING -> updateSendingStatus() - ReadStatus.FAILED -> updateReadStatus( - R.drawable.ic_baseline_close_24, - "failed" - ) - else -> null - } - - + // CoroutineScope(Dispatchers.Main).launch { + if (message.sendingFailed) { + updateStatus( + R.drawable.baseline_report_problem_24, + "failed" + ) + // } else if (message.isTempMessage && !networkMonitor.isOnline.first()) { + // updateStatus( + // R.drawable.ic_signal_wifi_off_white_24dp, + // "offline" + // ) + } else if (message.isTempMessage) { + updateSendingStatus() + } else if(message.readStatus == ReadStatus.READ){ + updateStatus(R.drawable.ic_check_all, context.resources?.getString(R.string.nc_message_read)) + } else if(message.readStatus == ReadStatus.SENT) { + updateStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent)) + } + // } itemView.setTag(R.string.replyable_message_view_tag, message.replyable) @@ -141,7 +153,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : ) } - private fun updateReadStatus(readStatusDrawableInt: Int, description: String?) { + private fun updateStatus(readStatusDrawableInt: Int, description: String?) { binding.sendingProgress.visibility = View.GONE binding.checkMark.visibility = View.VISIBLE readStatusDrawableInt.let { drawableInt -> diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageViewHolder.kt index 73552c8c7..a8e6a8f4f 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageViewHolder.kt @@ -13,6 +13,7 @@ import android.view.View import androidx.core.content.res.ResourcesCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.isVisible import autodagger.AutoInjector import coil.load import com.nextcloud.android.common.ui.theme.utils.ColorRole @@ -58,6 +59,14 @@ class TemporaryMessageViewHolder(outgoingView: View, payload: Any) : viewThemeUtils.platform.colorImageView(binding.tempMsgEdit, ColorRole.PRIMARY) viewThemeUtils.platform.colorImageView(binding.tempMsgDelete, ColorRole.PRIMARY) + binding.bubble.setOnClickListener { + if (binding.tempMsgActions.isVisible) { + binding.tempMsgActions.visibility = View.GONE + } else { + binding.tempMsgActions.visibility = View.VISIBLE + } + } + binding.tempMsgEdit.setOnClickListener { isEditing = !isEditing if (isEditing) { diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index e786832bc..d2d281b0e 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -2934,12 +2934,7 @@ class ChatActivity : ) { if (message.item is ChatMessage) { val chatMessage = message.item as ChatMessage - - if (chatMessage.sendingFailed) { - chatMessage.readStatus = ReadStatus.FAILED - } else if (chatMessage.isTempMessage) { - chatMessage.readStatus = ReadStatus.SENDING - } else if (chatMessage.jsonMessageId <= xChatLastCommonRead) { + if (chatMessage.jsonMessageId <= xChatLastCommonRead) { chatMessage.readStatus = ReadStatus.READ } else { chatMessage.readStatus = ReadStatus.SENT @@ -3870,6 +3865,7 @@ class ChatActivity : CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == UNREAD_MESSAGES_MARKER_ID.toString() CONTENT_TYPE_CALL_STARTED -> message.id == "-2" CONTENT_TYPE_TEMP -> message.id == TEMPORARY_MESSAGE_ID_STRING + // CONTENT_TYPE_TEMP -> message.readStatus == ReadStatus.FAILED CONTENT_TYPE_DECK_CARD -> message.isDeckCard() else -> false diff --git a/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt index 1b46d5e89..5349794c3 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt @@ -52,6 +52,7 @@ data class ChatMessageEntity( @ColumnInfo(name = "deleted") var deleted: Boolean = false, @ColumnInfo(name = "expirationTimestamp") var expirationTimestamp: Int = 0, @ColumnInfo(name = "isReplyable") var replyable: Boolean = false, + @ColumnInfo(name = "isTemporary") var isTemporary: Boolean = false, @ColumnInfo(name = "lastEditActorDisplayName") var lastEditActorDisplayName: String? = null, @ColumnInfo(name = "lastEditActorId") var lastEditActorId: String? = null, @ColumnInfo(name = "lastEditActorType") var lastEditActorType: String? = null, @@ -63,9 +64,8 @@ data class ChatMessageEntity( @ColumnInfo(name = "reactions") var reactions: LinkedHashMap? = null, @ColumnInfo(name = "reactionsSelf") var reactionsSelf: ArrayList? = null, @ColumnInfo(name = "referenceId") var referenceId: String? = null, + @ColumnInfo(name = "sendingFailed") var sendingFailed: Boolean = false, @ColumnInfo(name = "systemMessage") var systemMessageType: ChatMessage.SystemMessageType, @ColumnInfo(name = "timestamp") var timestamp: Long = 0, - @ColumnInfo(name = "isTemporary") var isTemporary: Boolean = false, - @ColumnInfo(name = "sendingFailed") var sendingFailed: Boolean = false, // missing/not needed: silent ) diff --git a/app/src/main/res/drawable/baseline_replay_24.xml b/app/src/main/res/drawable/baseline_replay_24.xml new file mode 100644 index 000000000..58390a9c3 --- /dev/null +++ b/app/src/main/res/drawable/baseline_replay_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_report_problem_24.xml b/app/src/main/res/drawable/baseline_report_problem_24.xml new file mode 100644 index 000000000..a17ce9ad9 --- /dev/null +++ b/app/src/main/res/drawable/baseline_report_problem_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/item_custom_outcoming_text_message.xml b/app/src/main/res/layout/item_custom_outcoming_text_message.xml index 7c93c5d2a..4ada0dcd8 100644 --- a/app/src/main/res/layout/item_custom_outcoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_outcoming_text_message.xml @@ -16,6 +16,42 @@ android:layout_marginRight="16dp" android:layout_marginBottom="2dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + app:tint="@color/high_emphasis_text" + tools:src="@drawable/ic_check_all" /> + app:tint="@color/high_emphasis_text" + tools:src="@drawable/ic_warning_white"/> + android:layout_below="@id/bubble" + android:layout_alignParentEnd="true"> + + + tools:visibility="gone"/> + android:visibility="gone" + tools:visibility="visible"/> From 0f53244652358493653e352d58ca5c6e3dde29aa Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Wed, 11 Dec 2024 15:27:52 +0100 Subject: [PATCH 10/40] prepare to replace no-internet-connection message handling (sorry Julius!!) Signed-off-by: Marcel Hibbe --- .../OutcomingTextMessageViewHolder.kt | 14 +- .../com/nextcloud/talk/chat/ChatActivity.kt | 69 ++++--- .../talk/chat/MessageInputFragment.kt | 35 ++-- .../chat/viewmodels/MessageInputViewModel.kt | 170 +++++++++--------- .../talk/jobs/AccountRemovalWorker.java | 1 - .../utils/preferences/AppPreferences.java | 6 - .../utils/preferences/AppPreferencesImpl.kt | 136 +++++++------- 7 files changed, 211 insertions(+), 220 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 6d995febd..ea29e7960 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -120,17 +120,17 @@ class OutcomingTextMessageViewHolder(itemView: View) : } - // CoroutineScope(Dispatchers.Main).launch { + CoroutineScope(Dispatchers.Main).launch { if (message.sendingFailed) { updateStatus( R.drawable.baseline_report_problem_24, "failed" ) - // } else if (message.isTempMessage && !networkMonitor.isOnline.first()) { - // updateStatus( - // R.drawable.ic_signal_wifi_off_white_24dp, - // "offline" - // ) + } else if (message.isTempMessage && !networkMonitor.isOnline.first()) { + updateStatus( + R.drawable.ic_signal_wifi_off_white_24dp, + "offline" + ) } else if (message.isTempMessage) { updateSendingStatus() } else if(message.readStatus == ReadStatus.READ){ @@ -138,7 +138,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : } else if(message.readStatus == ReadStatus.SENT) { updateStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent)) } - // } + } itemView.setTag(R.string.replyable_message_view_tag, message.replyable) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index d2d281b0e..3a73dc130 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -583,21 +583,21 @@ class ChatActivity : private fun initObservers() { Log.d(TAG, "initObservers Called") - messageInputViewModel.messageQueueFlow.observe(this) { list -> - list.forEachIndexed { _, qMsg -> - val temporaryChatMessage = ChatMessage() - temporaryChatMessage.jsonMessageId = TEMPORARY_MESSAGE_ID_INT - temporaryChatMessage.actorId = TEMPORARY_MESSAGE_ID_STRING - temporaryChatMessage.timestamp = System.currentTimeMillis() / ONE_SECOND_IN_MILLIS - temporaryChatMessage.message = qMsg.message.toString() - temporaryChatMessage.tempMessageId = qMsg.id - temporaryChatMessage.isTempMessage = true - temporaryChatMessage.parentMessageId = qMsg.replyTo!!.toLong() - val pos = adapter?.getMessagePositionById(qMsg.replyTo.toString()) - adapter?.addToStart(temporaryChatMessage, true) - adapter?.notifyDataSetChanged() - } - } + // messageInputViewModel.messageQueueFlow.observe(this) { list -> + // list.forEachIndexed { _, qMsg -> + // val temporaryChatMessage = ChatMessage() + // temporaryChatMessage.jsonMessageId = TEMPORARY_MESSAGE_ID_INT + // temporaryChatMessage.actorId = TEMPORARY_MESSAGE_ID_STRING + // temporaryChatMessage.timestamp = System.currentTimeMillis() / ONE_SECOND_IN_MILLIS + // temporaryChatMessage.message = qMsg.message.toString() + // temporaryChatMessage.tempMessageId = qMsg.id + // temporaryChatMessage.isTempMessage = true + // temporaryChatMessage.parentMessageId = qMsg.replyTo!!.toLong() + // val pos = adapter?.getMessagePositionById(qMsg.replyTo.toString()) + // adapter?.addToStart(temporaryChatMessage, true) + // adapter?.notifyDataSetChanged() + // } + // } messageInputViewModel.messageQueueSizeFlow.observe(this) { size -> if (size == 0) { @@ -719,7 +719,6 @@ class ChatActivity : withCredentials = credentials!!, withUrl = urlForChatting ) - messageInputViewModel.getTempMessagesFromMessageQueue(currentConversation!!.internalId) } } else { Log.w( @@ -3864,7 +3863,7 @@ class ChatActivity : CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage) CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == UNREAD_MESSAGES_MARKER_ID.toString() CONTENT_TYPE_CALL_STARTED -> message.id == "-2" - CONTENT_TYPE_TEMP -> message.id == TEMPORARY_MESSAGE_ID_STRING + // CONTENT_TYPE_TEMP -> message.id == TEMPORARY_MESSAGE_ID_STRING // CONTENT_TYPE_TEMP -> message.readStatus == ReadStatus.FAILED CONTENT_TYPE_DECK_CARD -> message.isDeckCard() @@ -4013,27 +4012,27 @@ class ChatActivity : } override fun editTemporaryMessage(id: Int, newMessage: String) { - messageInputViewModel.editQueuedMessage(currentConversation!!.internalId, id, newMessage) - adapter?.notifyDataSetChanged() // TODO optimize this + // messageInputViewModel.editQueuedMessage(currentConversation!!.internalId, id, newMessage) + // adapter?.notifyDataSetChanged() // TODO optimize this } override fun deleteTemporaryMessage(id: Int) { - messageInputViewModel.removeFromQueue(currentConversation!!.internalId, id) - var i = 0 - val max = messageInputViewModel.messageQueueSizeFlow.value?.plus(1) - for (item in adapter?.items!!) { - if (i > max!! && max < 1) break - if (item.item is ChatMessage && - (item.item as ChatMessage).isTempMessage && - (item.item as ChatMessage).tempMessageId == id - ) { - val index = adapter?.items!!.indexOf(item) - adapter?.items!!.removeAt(index) - adapter?.notifyItemRemoved(index) - break - } - i++ - } + // messageInputViewModel.removeFromQueue(currentConversation!!.internalId, id) + // var i = 0 + // val max = messageInputViewModel.messageQueueSizeFlow.value?.plus(1) + // for (item in adapter?.items!!) { + // if (i > max!! && max < 1) break + // if (item.item is ChatMessage && + // (item.item as ChatMessage).isTempMessage && + // (item.item as ChatMessage).tempMessageId == id + // ) { + // val index = adapter?.items!!.indexOf(item) + // adapter?.items!!.removeAt(index) + // adapter?.notifyItemRemoved(index) + // break + // } + // i++ + // } } private fun logConversationInfos(methodName: String) { diff --git a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt index b97ca345b..dba84eaed 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -158,7 +158,6 @@ class MessageInputFragment : Fragment() { override fun onResume() { super.onResume() - chatActivity.messageInputViewModel.restoreMessageQueue(conversationInternalId) } override fun onDestroyView() { @@ -199,7 +198,7 @@ class MessageInputFragment : Fragment() { wasOnline = !binding.fragmentConnectionLost.isShown val connectionGained = (!wasOnline && isOnline) Log.d(TAG, "isOnline: $isOnline\nwasOnline: $wasOnline\nconnectionGained: $connectionGained") - handleMessageQueue(isOnline) + // handleMessageQueue(isOnline) handleUI(isOnline, connectionGained) }.collect() } @@ -292,22 +291,22 @@ class MessageInputFragment : Fragment() { } } - private fun handleMessageQueue(isOnline: Boolean) { - if (isOnline) { - chatActivity.messageInputViewModel.switchToMessageQueue(false) - chatActivity.messageInputViewModel.sendAndEmptyMessageQueue( - conversationInternalId, - chatActivity.conversationUser!!.getCredentials(), - ApiUtils.getUrlForChat( - chatActivity.chatApiVersion, - chatActivity.conversationUser!!.baseUrl!!, - chatActivity.roomToken - ) - ) - } else { - chatActivity.messageInputViewModel.switchToMessageQueue(true) - } - } + // private fun handleMessageQueue(isOnline: Boolean) { + // if (isOnline) { + // chatActivity.messageInputViewModel.switchToMessageQueue(false) + // chatActivity.messageInputViewModel.sendAndEmptyMessageQueue( + // conversationInternalId, + // chatActivity.conversationUser!!.getCredentials(), + // ApiUtils.getUrlForChat( + // chatActivity.chatApiVersion, + // chatActivity.conversationUser!!.baseUrl!!, + // chatActivity.roomToken + // ) + // ) + // } else { + // chatActivity.messageInputViewModel.switchToMessageQueue(true) + // } + // } private fun restoreState() { if (binding.fragmentMessageInputView.inputEditText.text.isEmpty()) { diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index c00e89546..8bc2f9e88 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -55,16 +55,16 @@ class MessageInputViewModel @Inject constructor( chatRepository = chatMessageRepository } - data class QueuedMessage( - val id: Int, - var message: CharSequence? = null, - val displayName: String? = null, - val replyTo: Int? = null, - val sendWithoutNotification: Boolean? = null - ) + // data class QueuedMessage( + // val id: Int, + // var message: CharSequence? = null, + // val displayName: String? = null, + // val replyTo: Int? = null, + // val sendWithoutNotification: Boolean? = null + // ) - private var isQueueing: Boolean = false - private var messageQueue: MutableList = mutableListOf() + // private var isQueueing: Boolean = false + // private var messageQueue: MutableList = mutableListOf() override fun onResume(owner: LifecycleOwner) { super.onResume(owner) @@ -129,13 +129,13 @@ class MessageInputViewModel @Inject constructor( val isVoicePreviewPlaying: LiveData get() = _isVoicePreviewPlaying - private val _messageQueueSizeFlow = MutableStateFlow(messageQueue.size) + private val _messageQueueSizeFlow = MutableStateFlow(666) val messageQueueSizeFlow: LiveData get() = _messageQueueSizeFlow.asLiveData() - private val _messageQueueFlow: MutableLiveData> = MutableLiveData() - val messageQueueFlow: LiveData> - get() = _messageQueueFlow + // private val _messageQueueFlow: MutableLiveData> = MutableLiveData() + // val messageQueueFlow: LiveData> + // get() = _messageQueueFlow private val _callStartedFlow: MutableLiveData> = MutableLiveData() val callStartedFlow: LiveData> @@ -171,16 +171,16 @@ class MessageInputViewModel @Inject constructor( } } - if (isQueueing) { - val tempID = System.currentTimeMillis().toInt() - val qMsg = QueuedMessage(tempID, message, displayName, replyTo, sendWithoutNotification) - messageQueue = appPreferences.getMessageQueue(internalId) - messageQueue.add(qMsg) - appPreferences.saveMessageQueue(internalId, messageQueue) - _messageQueueSizeFlow.update { messageQueue.size } - _messageQueueFlow.postValue(listOf(qMsg)) - return - } + // if (isQueueing) { + // val tempID = System.currentTimeMillis().toInt() + // val qMsg = QueuedMessage(tempID, message, displayName, replyTo, sendWithoutNotification) + // messageQueue = appPreferences.getMessageQueue(internalId) + // messageQueue.add(qMsg) + // appPreferences.saveMessageQueue(internalId, messageQueue) + // _messageQueueSizeFlow.update { messageQueue.size } + // _messageQueueFlow.postValue(listOf(qMsg)) + // return + // } viewModelScope.launch { chatRepository.sendChatMessage( @@ -268,68 +268,68 @@ class MessageInputViewModel @Inject constructor( _getRecordingTime.postValue(time) } - fun sendAndEmptyMessageQueue(internalId: String, credentials: String, url: String) { - if (isQueueing) return - messageQueue.clear() - - val queue = appPreferences.getMessageQueue(internalId) - appPreferences.saveMessageQueue(internalId, null) // empties the queue - while (queue.size > 0) { - val msg = queue.removeAt(0) - sendChatMessage( - internalId, - credentials, - url, - msg.message!!, - msg.displayName!!, - msg.replyTo!!, - msg.sendWithoutNotification!! - ) - sleep(DELAY_BETWEEN_QUEUED_MESSAGES) - } - _messageQueueSizeFlow.tryEmit(0) - } - - fun getTempMessagesFromMessageQueue(internalId: String) { - val queue = appPreferences.getMessageQueue(internalId) - val list = mutableListOf() - for (msg in queue) { - list.add(msg) - } - _messageQueueFlow.postValue(list) - } - - fun switchToMessageQueue(shouldQueue: Boolean) { - isQueueing = shouldQueue - } - - fun restoreMessageQueue(internalId: String) { - messageQueue = appPreferences.getMessageQueue(internalId) - _messageQueueSizeFlow.tryEmit(messageQueue.size) - } - - fun removeFromQueue(internalId: String, id: Int) { - val queue = appPreferences.getMessageQueue(internalId) - for (qMsg in queue) { - if (qMsg.id == id) { - queue.remove(qMsg) - break - } - } - appPreferences.saveMessageQueue(internalId, queue) - _messageQueueSizeFlow.tryEmit(queue.size) - } - - fun editQueuedMessage(internalId: String, id: Int, newMessage: String) { - val queue = appPreferences.getMessageQueue(internalId) - for (qMsg in queue) { - if (qMsg.id == id) { - qMsg.message = newMessage - break - } - } - appPreferences.saveMessageQueue(internalId, queue) - } + // fun sendAndEmptyMessageQueue(internalId: String, credentials: String, url: String) { + // if (isQueueing) return + // messageQueue.clear() + // + // val queue = appPreferences.getMessageQueue(internalId) + // appPreferences.saveMessageQueue(internalId, null) // empties the queue + // while (queue.size > 0) { + // val msg = queue.removeAt(0) + // sendChatMessage( + // internalId, + // credentials, + // url, + // msg.message!!, + // msg.displayName!!, + // msg.replyTo!!, + // msg.sendWithoutNotification!! + // ) + // sleep(DELAY_BETWEEN_QUEUED_MESSAGES) + // } + // _messageQueueSizeFlow.tryEmit(0) + // } + // + // fun getTempMessagesFromMessageQueue(internalId: String) { + // val queue = appPreferences.getMessageQueue(internalId) + // val list = mutableListOf() + // for (msg in queue) { + // list.add(msg) + // } + // _messageQueueFlow.postValue(list) + // } + // + // fun switchToMessageQueue(shouldQueue: Boolean) { + // isQueueing = shouldQueue + // } + // + // fun restoreMessageQueue(internalId: String) { + // messageQueue = appPreferences.getMessageQueue(internalId) + // _messageQueueSizeFlow.tryEmit(messageQueue.size) + // } + // + // fun removeFromQueue(internalId: String, id: Int) { + // val queue = appPreferences.getMessageQueue(internalId) + // for (qMsg in queue) { + // if (qMsg.id == id) { + // queue.remove(qMsg) + // break + // } + // } + // appPreferences.saveMessageQueue(internalId, queue) + // _messageQueueSizeFlow.tryEmit(queue.size) + // } + // + // fun editQueuedMessage(internalId: String, id: Int, newMessage: String) { + // val queue = appPreferences.getMessageQueue(internalId) + // for (qMsg in queue) { + // if (qMsg.id == id) { + // qMsg.message = newMessage + // break + // } + // } + // appPreferences.saveMessageQueue(internalId, queue) + // } fun showCallStartedIndicator(recent: ChatMessage, show: Boolean) { _callStartedFlow.postValue(Pair(recent, show)) diff --git a/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java b/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java index 6f273ab32..8679f6504 100644 --- a/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java +++ b/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java @@ -192,7 +192,6 @@ public class AccountRemovalWorker extends Worker { if (user.getId() != null) { String username = user.getUsername(); try { - appPreferences.deleteAllMessageQueuesFor(user.getUserId()); userManager.deleteUser(user.getId()); Log.d(TAG, "deleted user: " + username); } catch (Throwable e) { diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java index a9265ae8e..beb152a69 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferences.java @@ -175,12 +175,6 @@ public interface AppPreferences { int getLastKnownId(String internalConversationId, int defaultValue); - void saveMessageQueue(String internalConversationId, List queue); - - List getMessageQueue(String internalConversationId); - - void deleteAllMessageQueuesFor(String userId); - void saveVoiceMessagePlaybackSpeedPreferences(Map speeds); Map readVoiceMessagePlaybackSpeedPreferences(); diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt index c1e611b9a..742b27c26 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt @@ -501,75 +501,75 @@ class AppPreferencesImpl(val context: Context) : AppPreferences { return if (lastReadId.isNotEmpty()) lastReadId.toInt() else defaultValue } - override fun saveMessageQueue( - internalConversationId: String, - queue: MutableList? - ) { - runBlocking { - async { - var queueStr = "" - queue?.let { - for (msg in queue) { - val msgStr = "${msg.id},${msg.message},${msg.replyTo},${msg.displayName},${ - msg - .sendWithoutNotification - }^" - queueStr += msgStr - } - } - writeString(internalConversationId + MESSAGE_QUEUE, queueStr) - } - } - } + // override fun saveMessageQueue( + // internalConversationId: String, + // queue: MutableList? + // ) { + // runBlocking { + // async { + // var queueStr = "" + // queue?.let { + // for (msg in queue) { + // val msgStr = "${msg.id},${msg.message},${msg.replyTo},${msg.displayName},${ + // msg + // .sendWithoutNotification + // }^" + // queueStr += msgStr + // } + // } + // writeString(internalConversationId + MESSAGE_QUEUE, queueStr) + // } + // } + // } + // + // @Suppress("Detekt.TooGenericExceptionCaught") + // override fun getMessageQueue(internalConversationId: String): MutableList { + // val queueStr = + // runBlocking { async { readString(internalConversationId + MESSAGE_QUEUE).first() } }.getCompleted() + // + // val queue: MutableList = mutableListOf() + // if (queueStr.isEmpty()) return queue + // + // for (msgStr in queueStr.split("^")) { + // try { + // if (msgStr.isNotEmpty()) { + // val msgArray = msgStr.split(",") + // val id = msgArray[ID].toInt() + // val message = msgArray[MESSAGE_INDEX] + // val replyTo = msgArray[REPLY_TO_INDEX].toInt() + // val displayName = msgArray[DISPLAY_NAME_INDEX] + // val silent = msgArray[SILENT_INDEX].toBoolean() + // + // val qMsg = MessageInputViewModel.QueuedMessage(id, message, displayName, replyTo, silent) + // queue.add(qMsg) + // } + // } catch (e: IndexOutOfBoundsException) { + // Log.e(TAG, "Message string: $msgStr\n Queue String: $queueStr \n$e") + // } + // } + // + // return queue + // } - @Suppress("Detekt.TooGenericExceptionCaught") - override fun getMessageQueue(internalConversationId: String): MutableList { - val queueStr = - runBlocking { async { readString(internalConversationId + MESSAGE_QUEUE).first() } }.getCompleted() - - val queue: MutableList = mutableListOf() - if (queueStr.isEmpty()) return queue - - for (msgStr in queueStr.split("^")) { - try { - if (msgStr.isNotEmpty()) { - val msgArray = msgStr.split(",") - val id = msgArray[ID].toInt() - val message = msgArray[MESSAGE_INDEX] - val replyTo = msgArray[REPLY_TO_INDEX].toInt() - val displayName = msgArray[DISPLAY_NAME_INDEX] - val silent = msgArray[SILENT_INDEX].toBoolean() - - val qMsg = MessageInputViewModel.QueuedMessage(id, message, displayName, replyTo, silent) - queue.add(qMsg) - } - } catch (e: IndexOutOfBoundsException) { - Log.e(TAG, "Message string: $msgStr\n Queue String: $queueStr \n$e") - } - } - - return queue - } - - override fun deleteAllMessageQueuesFor(userId: String) { - runBlocking { - async { - val keyList = mutableListOf>() - val preferencesMap = context.dataStore.data.first().asMap() - for (preference in preferencesMap) { - if (preference.key.name.contains("$userId@")) { - keyList.add(preference.key) - } - } - - for (key in keyList) { - context.dataStore.edit { - it.remove(key) - } - } - } - } - } + // override fun deleteAllMessageQueuesFor(userId: String) { + // runBlocking { + // async { + // val keyList = mutableListOf>() + // val preferencesMap = context.dataStore.data.first().asMap() + // for (preference in preferencesMap) { + // if (preference.key.name.contains("$userId@")) { + // keyList.add(preference.key) + // } + // } + // + // for (key in keyList) { + // context.dataStore.edit { + // it.remove(key) + // } + // } + // } + // } + // } override fun saveVoiceMessagePlaybackSpeedPreferences(speeds: Map) { Json.encodeToString(speeds).let { From ec466e58f0f5c83caeb1edbfc99380518ff8278e Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Sun, 15 Dec 2024 19:51:55 +0100 Subject: [PATCH 11/40] replace CharSequence with String for sendChatMessage Signed-off-by: Marcel Hibbe --- .../OutcomingTextMessageViewHolder.kt | 20 ++++---- .../com/nextcloud/talk/api/NcApiCoroutines.kt | 2 +- .../talk/chat/MessageInputFragment.kt | 4 +- .../talk/chat/data/ChatMessageRepository.kt | 2 +- .../data/network/ChatNetworkDataSource.kt | 2 +- .../network/OfflineFirstChatRepository.kt | 48 +++++++++++++++---- .../chat/data/network/RetrofitChatNetwork.kt | 2 +- .../chat/viewmodels/MessageInputViewModel.kt | 2 +- .../item_custom_outcoming_text_message.xml | 7 --- 9 files changed, 57 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index ea29e7960..d289933ba 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -14,7 +14,6 @@ import android.util.Log import android.util.TypedValue import android.view.View import androidx.core.content.res.ResourcesCompat -import androidx.lifecycle.lifecycleScope import autodagger.AutoInjector import coil.load import com.google.android.flexbox.FlexboxLayout @@ -73,6 +72,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : layoutParams.isWrapBefore = false var textSize = context.resources.getDimension(R.dimen.chat_text_size) viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT) + var processedMessageText = messageUtils.enrichChatMessageText( binding.messageText.context, message, @@ -121,18 +121,20 @@ class OutcomingTextMessageViewHolder(itemView: View) : CoroutineScope(Dispatchers.Main).launch { - if (message.sendingFailed) { - updateStatus( - R.drawable.baseline_report_problem_24, - "failed" - ) - } else if (message.isTempMessage && !networkMonitor.isOnline.first()) { + // if (message.sendingFailed) { + // updateStatus( + // R.drawable.baseline_report_problem_24, + // "failed" + // ) + // } else + + if (message.isTempMessage && !networkMonitor.isOnline.first()) { updateStatus( R.drawable.ic_signal_wifi_off_white_24dp, "offline" ) } else if (message.isTempMessage) { - updateSendingStatus() + showSendingSpinner() } else if(message.readStatus == ReadStatus.READ){ updateStatus(R.drawable.ic_check_all, context.resources?.getString(R.string.nc_message_read)) } else if(message.readStatus == ReadStatus.SENT) { @@ -165,7 +167,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : binding.checkMark.contentDescription = description } - private fun updateSendingStatus() { + private fun showSendingSpinner() { binding.sendingProgress.visibility = View.VISIBLE binding.checkMark.visibility = View.GONE diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt index db6a00c55..a1b027727 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt +++ b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt @@ -128,7 +128,7 @@ interface NcApiCoroutines { suspend fun sendChatMessage( @Header("Authorization") authorization: String, @Url url: String, - @Field("message") message: CharSequence, + @Field("message") message: String, @Field("actorDisplayName") actorDisplayName: String, @Field("replyTo") replyTo: Int, @Field("silent") sendWithoutNotification: Boolean, diff --git a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt index dba84eaed..5e8ee1bc1 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -867,7 +867,7 @@ class MessageInputFragment : Fragment() { .findViewById(R.id.quotedChatMessageView)?.tag as Int? ?: 0 sendMessage( - editable, + editable.toString(), replyMessageId, sendWithoutNotification ) @@ -875,7 +875,7 @@ class MessageInputFragment : Fragment() { } } - private fun sendMessage(message: CharSequence, replyTo: Int?, sendWithoutNotification: Boolean) { + private fun sendMessage(message: String, replyTo: Int?, sendWithoutNotification: Boolean) { chatActivity.messageInputViewModel.sendChatMessage( conversationInternalId, chatActivity.conversationUser!!.getCredentials(), diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt index 976f3db94..630358040 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt @@ -82,7 +82,7 @@ interface ChatMessageRepository : LifecycleAwareManager { suspend fun sendChatMessage( credentials: String, url: String, - message: CharSequence, + message: String, displayName: String, replyTo: Int, sendWithoutNotification: Boolean, diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt index f6e02e9d3..a34337aa9 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt @@ -53,7 +53,7 @@ interface ChatNetworkDataSource { suspend fun sendChatMessage( credentials: String, url: String, - message: CharSequence, + message: String, displayName: String, replyTo: Int, sendWithoutNotification: Boolean, diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 7db771a2b..f08b278b4 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -13,6 +13,10 @@ import android.util.Log import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.data.ChatMessageRepository import com.nextcloud.talk.chat.data.model.ChatMessage +import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel +import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel.Companion +import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel.SendChatMessageErrorState +import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel.SendChatMessageSuccessState import com.nextcloud.talk.data.database.dao.ChatBlocksDao import com.nextcloud.talk.data.database.dao.ChatMessagesDao import com.nextcloud.talk.data.database.mappers.asEntity @@ -185,7 +189,7 @@ class OfflineFirstChatRepository @Inject constructor( limit ) if (list.isNotEmpty()) { - updateUiMessages( + handleNewAndTempMessages( receivedChatMessages = list, lookIntoFuture = false, showUnreadMessagesMarker = false @@ -307,7 +311,7 @@ class OfflineFirstChatRepository @Inject constructor( val weHaveMessagesFromOurself = chatMessages.any { it.actorId == currentUser.userId } showUnreadMessagesMarker = showUnreadMessagesMarker && !weHaveMessagesFromOurself - updateUiMessages( + handleNewAndTempMessages( receivedChatMessages = chatMessages, lookIntoFuture = true, showUnreadMessagesMarker = showUnreadMessagesMarker @@ -334,7 +338,33 @@ class OfflineFirstChatRepository @Inject constructor( } } - private suspend fun updateUiMessages( + // TODO replace with WorkManager? + // private suspend fun tryToSendPendingMessages() { + // val tempMessages = chatDao.getTempMessagesForConversation(internalConversationId).first() + // + // tempMessages.forEach { + // Log.d(TAG, "Sending chat message ${it.message} another time!!") + // + // sendChatMessage( + // credentials, + // urlForChatting, + // it.message, + // it.actorDisplayName, + // it.parentMessageId?.toInt() ?: 0, + // false, + // it.referenceId ?: "" + // ).collect { result -> + // if (result.isSuccess) { + // Log.d(TAG, "success. received ref id: " + (result.getOrNull()?.referenceId ?: "none")) + // + // } else { + // Log.d(TAG, "fail. received ref id: " + (result.getOrNull()?.referenceId ?: "none")) + // } + // } + // } + // } + + private suspend fun handleNewAndTempMessages( receivedChatMessages : List, lookIntoFuture: Boolean, showUnreadMessagesMarker: Boolean @@ -792,7 +822,7 @@ class OfflineFirstChatRepository @Inject constructor( override suspend fun sendChatMessage( credentials: String, url: String, - message: CharSequence, + message: String, displayName: String, replyTo: Int, sendWithoutNotification: Boolean, @@ -820,11 +850,11 @@ class OfflineFirstChatRepository @Inject constructor( failedMessage.sendingFailed = true chatDao.updateChatMessage(failedMessage) - val failedMessageModel = failedMessage.asModel() - _removeMessageFlow.emit(failedMessageModel) - - val tripleChatMessages = Triple(true, false, listOf(failedMessageModel)) - _messageFlow.emit(tripleChatMessages) + // val failedMessageModel = failedMessage.asModel() + // _removeMessageFlow.emit(failedMessageModel) + // + // val tripleChatMessages = Triple(true, false, listOf(failedMessageModel)) + // _messageFlow.emit(tripleChatMessages) emit(Result.failure(e)) } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt index 33211a1af..27e96517e 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt @@ -144,7 +144,7 @@ class RetrofitChatNetwork( override suspend fun sendChatMessage( credentials: String, url: String, - message: CharSequence, + message: String, displayName: String, replyTo: Int, sendWithoutNotification: Boolean, diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 8bc2f9e88..238a6f54d 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -146,7 +146,7 @@ class MessageInputViewModel @Inject constructor( internalId: String, credentials: String, url: String, - message: CharSequence, + message: String, displayName: String, replyTo: Int, sendWithoutNotification: Boolean diff --git a/app/src/main/res/layout/item_custom_outcoming_text_message.xml b/app/src/main/res/layout/item_custom_outcoming_text_message.xml index 4ada0dcd8..de86bd000 100644 --- a/app/src/main/res/layout/item_custom_outcoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_outcoming_text_message.xml @@ -79,13 +79,6 @@ android:textIsSelectable="false" tools:text="Talk to you later!" /> - - - - - - - Date: Fri, 20 Dec 2024 18:06:30 +0100 Subject: [PATCH 12/40] WIP add options to temp messages TODO: check id type --> see TODO "currentTimeMillies fails as id because later on in the model it's not Long but Int!!!!" in OfflineFirstChatRepository.kt Signed-off-by: Marcel Hibbe --- .../OutcomingTextMessageViewHolder.kt | 22 +- .../messages/TalkMessagesListAdapter.java | 3 - .../messages/TemporaryMessageInterface.kt | 13 -- .../messages/TemporaryMessageViewHolder.kt | 206 ------------------ .../com/nextcloud/talk/chat/ChatActivity.kt | 98 +++------ .../talk/chat/MessageInputFragment.kt | 27 ++- .../talk/chat/data/ChatMessageRepository.kt | 3 + .../talk/chat/data/model/ChatMessage.kt | 4 +- .../network/OfflineFirstChatRepository.kt | 114 +++++----- .../chat/viewmodels/MessageInputViewModel.kt | 15 ++ .../talk/data/database/dao/ChatMessagesDao.kt | 5 +- .../database/mappers/ChatMessageMapUtils.kt | 2 +- .../ui/dialog/TempMessageActionsDialog.kt | 124 +++++++++++ .../layout/dialog_temp_message_actions.xml | 132 +++++++++++ 14 files changed, 402 insertions(+), 366 deletions(-) delete mode 100644 app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageInterface.kt delete mode 100644 app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageViewHolder.kt create mode 100644 app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt create mode 100644 app/src/main/res/layout/dialog_temp_message_actions.xml diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index d289933ba..6e970b799 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -121,21 +121,23 @@ class OutcomingTextMessageViewHolder(itemView: View) : CoroutineScope(Dispatchers.Main).launch { - // if (message.sendingFailed) { - // updateStatus( - // R.drawable.baseline_report_problem_24, - // "failed" - // ) - // } else - - if (message.isTempMessage && !networkMonitor.isOnline.first()) { + if (message.isTemporary && !networkMonitor.isOnline.first()) { updateStatus( R.drawable.ic_signal_wifi_off_white_24dp, "offline" ) - } else if (message.isTempMessage) { + } else if (message.sendingFailed) { + updateStatus( + R.drawable.baseline_report_problem_24, + "failed" + ) + binding.bubble.setOnClickListener { + commonMessageInterface.onOpenMessageActionsDialog(message) + } + + } else if (message.isTemporary) { showSendingSpinner() - } else if(message.readStatus == ReadStatus.READ){ + } else if(message.readStatus == ReadStatus.READ) { updateStatus(R.drawable.ic_check_all, context.resources?.getString(R.string.nc_message_read)) } else if(message.readStatus == ReadStatus.SENT) { updateStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent)) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java index 6eda1ff98..3d7609133 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/TalkMessagesListAdapter.java @@ -68,9 +68,6 @@ public class TalkMessagesListAdapter extends MessagesListAda } else if (holder instanceof SystemMessageViewHolder holderInstance) { holderInstance.assignSystemMessageInterface(chatActivity); - } else if (holder instanceof TemporaryMessageViewHolder holderInstance) { - holderInstance.assignTemporaryMessageInterface(chatActivity); - } else if (holder instanceof IncomingDeckCardViewHolder holderInstance) { holderInstance.assignCommonMessageInterface(chatActivity); } else if (holder instanceof OutcomingDeckCardViewHolder holderInstance) { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageInterface.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageInterface.kt deleted file mode 100644 index 44dab23af..000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageInterface.kt +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Nextcloud Talk - Android Client - * - * SPDX-FileCopyrightText: 2024 Julius Linus - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -package com.nextcloud.talk.adapters.messages - -interface TemporaryMessageInterface { - fun editTemporaryMessage(id: Int, newMessage: String) - fun deleteTemporaryMessage(id: Int) -} diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageViewHolder.kt deleted file mode 100644 index a8e6a8f4f..000000000 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/TemporaryMessageViewHolder.kt +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Nextcloud Talk - Android Client - * - * SPDX-FileCopyrightText: 2024 Julius Linus - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -package com.nextcloud.talk.adapters.messages - -import android.content.Context -import android.util.Log -import android.view.View -import androidx.core.content.res.ResourcesCompat -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.isVisible -import autodagger.AutoInjector -import coil.load -import com.nextcloud.android.common.ui.theme.utils.ColorRole -import com.nextcloud.talk.R -import com.nextcloud.talk.application.NextcloudTalkApplication -import com.nextcloud.talk.chat.ChatActivity -import com.nextcloud.talk.chat.data.model.ChatMessage -import com.nextcloud.talk.databinding.ItemTemporaryMessageBinding -import com.nextcloud.talk.ui.theme.ViewThemeUtils -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.DisplayUtils -import com.nextcloud.talk.utils.message.MessageUtils -import com.stfalcon.chatkit.messages.MessagesListAdapter -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import javax.inject.Inject - -@AutoInjector(NextcloudTalkApplication::class) -class TemporaryMessageViewHolder(outgoingView: View, payload: Any) : - MessagesListAdapter.OutcomingMessageViewHolder(outgoingView) { - - private val binding: ItemTemporaryMessageBinding = ItemTemporaryMessageBinding.bind(outgoingView) - - @Inject - lateinit var viewThemeUtils: ViewThemeUtils - - @Inject - lateinit var context: Context - - @Inject - lateinit var messageUtils: MessageUtils - - lateinit var temporaryMessageInterface: TemporaryMessageInterface - var isEditing = false - - override fun onBind(message: ChatMessage) { - super.onBind(message) - NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) - - viewThemeUtils.platform.colorImageView(binding.tempMsgEdit, ColorRole.PRIMARY) - viewThemeUtils.platform.colorImageView(binding.tempMsgDelete, ColorRole.PRIMARY) - - binding.bubble.setOnClickListener { - if (binding.tempMsgActions.isVisible) { - binding.tempMsgActions.visibility = View.GONE - } else { - binding.tempMsgActions.visibility = View.VISIBLE - } - } - - binding.tempMsgEdit.setOnClickListener { - isEditing = !isEditing - if (isEditing) { - binding.tempMsgEdit.setImageDrawable( - ResourcesCompat.getDrawable( - context.resources, - R.drawable.ic_check, - null - ) - ) - binding.messageEdit.visibility = View.VISIBLE - binding.messageEdit.requestFocus() - ViewCompat.getWindowInsetsController(binding.root)?.show(WindowInsetsCompat.Type.ime()) - binding.messageEdit.setText(binding.messageText.text) - binding.messageText.visibility = View.GONE - } else { - binding.tempMsgEdit.setImageDrawable( - ResourcesCompat.getDrawable( - context.resources, - R.drawable.ic_edit, - null - ) - ) - binding.messageEdit.visibility = View.GONE - binding.messageText.visibility = View.VISIBLE - val newMessage = binding.messageEdit.text.toString() - message.message = newMessage - temporaryMessageInterface.editTemporaryMessage(message.tempMessageId, newMessage) - } - } - - binding.tempMsgDelete.setOnClickListener { - temporaryMessageInterface.deleteTemporaryMessage(message.tempMessageId) - } - - // parent message handling - if (message.parentMessageId != null && message.parentMessageId!! > 0) { - processParentMessage(message) - binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE - } else { - binding.messageQuote.quotedChatMessageView.visibility = View.GONE - } - - val bgBubbleColor = bubble.resources.getColor(R.color.bg_message_list_incoming_bubble, null) - val layout = R.drawable.shape_outcoming_message - val bubbleDrawable = DisplayUtils.getMessageSelector( - bgBubbleColor, - ResourcesCompat.getColor(bubble.resources, R.color.transparent, null), - bgBubbleColor, - layout - ) - ViewCompat.setBackground(bubble, bubbleDrawable) - } - - @Suppress("Detekt.TooGenericExceptionCaught") - private fun processParentMessage(message: ChatMessage) { - if (message.parentMessageId != null && !message.isDeleted) { - CoroutineScope(Dispatchers.Main).launch { - try { - val chatActivity = temporaryMessageInterface as ChatActivity - val urlForChatting = ApiUtils.getUrlForChat( - chatActivity.chatApiVersion, - chatActivity.conversationUser?.baseUrl, - chatActivity.roomToken - ) - - val parentChatMessage = withContext(Dispatchers.IO) { - chatActivity.chatViewModel.getMessageById( - urlForChatting, - chatActivity.currentConversation!!, - message.parentMessageId!! - ).first() - } - - parentChatMessage.activeUser = message.activeUser - parentChatMessage.imageUrl?.let { - binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE - val placeholder = ResourcesCompat.getDrawable( - context.resources, - R.drawable.ic_mimetype_image, - null - ) - binding.messageQuote.quotedMessageImage.setImageDrawable(placeholder) - binding.messageQuote.quotedMessageImage.load(it) { - addHeader( - "Authorization", - ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!! - ) - } - } ?: run { - binding.messageQuote.quotedMessageImage.visibility = View.GONE - } - binding.messageQuote.quotedMessageAuthor.text = parentChatMessage.actorDisplayName - ?: context.getText(R.string.nc_nick_guest) - binding.messageQuote.quotedMessage.text = messageUtils - .enrichChatReplyMessageText( - binding.messageQuote.quotedMessage.context, - parentChatMessage, - false, - viewThemeUtils - ) - - viewThemeUtils.talk.colorOutgoingQuoteText(binding.messageQuote.quotedMessage) - viewThemeUtils.talk.colorOutgoingQuoteAuthorText(binding.messageQuote.quotedMessageAuthor) - viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView) - - binding.messageQuote.quotedChatMessageView.setOnClickListener { - val chatActivity = temporaryMessageInterface as ChatActivity - chatActivity.jumpToQuotedMessage(parentChatMessage) - } - } catch (e: Exception) { - Log.d(TAG, "Error when processing parent message in view holder", e) - } - } - } - } - - fun assignTemporaryMessageInterface(temporaryMessageInterface: TemporaryMessageInterface) { - this.temporaryMessageInterface = temporaryMessageInterface - } - - override fun viewDetached() { - // unused atm - } - - override fun viewAttached() { - // unused atm - } - - override fun viewRecycled() { - // unused atm - } - - companion object { - private val TAG = TemporaryMessageViewHolder::class.java.simpleName - } -} diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 3a73dc130..cfe4b36e9 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -111,8 +111,6 @@ import com.nextcloud.talk.adapters.messages.PreviewMessageViewHolder import com.nextcloud.talk.adapters.messages.SystemMessageInterface import com.nextcloud.talk.adapters.messages.SystemMessageViewHolder import com.nextcloud.talk.adapters.messages.TalkMessagesListAdapter -import com.nextcloud.talk.adapters.messages.TemporaryMessageInterface -import com.nextcloud.talk.adapters.messages.TemporaryMessageViewHolder import com.nextcloud.talk.adapters.messages.UnreadNoticeMessageViewHolder import com.nextcloud.talk.adapters.messages.VoiceMessageInterface import com.nextcloud.talk.api.NcApi @@ -154,6 +152,7 @@ import com.nextcloud.talk.ui.dialog.FileAttachmentPreviewFragment import com.nextcloud.talk.ui.dialog.MessageActionsDialog import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment import com.nextcloud.talk.ui.dialog.ShowReactionsDialog +import com.nextcloud.talk.ui.dialog.TempMessageActionsDialog import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback import com.nextcloud.talk.utils.ApiUtils @@ -230,8 +229,7 @@ class ChatActivity : CommonMessageInterface, PreviewMessageInterface, SystemMessageInterface, - CallStartedMessageInterface, - TemporaryMessageInterface { + CallStartedMessageInterface { var active = false @@ -600,16 +598,16 @@ class ChatActivity : // } messageInputViewModel.messageQueueSizeFlow.observe(this) { size -> - if (size == 0) { - var i = 0 - var pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING) - while (pos != null && pos > -1) { - adapter?.items?.removeAt(pos) - i++ - pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING) - } - adapter?.notifyDataSetChanged() - } + // if (size == 0) { + // var i = 0 + // var pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING) + // while (pos != null && pos > -1) { + // adapter?.items?.removeAt(pos) + // i++ + // pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING) + // } + // adapter?.notifyDataSetChanged() + // } } this.lifecycleScope.launch { @@ -1253,18 +1251,18 @@ class ChatActivity : viewThemeUtils.material.colorToolbarOverflowIcon(binding.chatToolbar) } - private fun getLastAdapterId(): Int { - var lastId = 0 - if (adapter?.items?.size != 0) { - val item = adapter?.items?.get(0)?.item - if (item != null) { - lastId = (item as ChatMessage).jsonMessageId - } else { - lastId = 0 - } - } - return lastId - } + // private fun getLastAdapterId(): Int { + // var lastId = 0 + // if (adapter?.items?.size != 0) { + // val item = adapter?.items?.get(0)?.item + // if (item != null) { + // lastId = (item as ChatMessage).jsonMessageId + // } else { + // lastId = 0 + // } + // } + // return lastId + // } private fun setupActionBar() { setSupportActionBar(binding.chatToolbar) @@ -1384,17 +1382,6 @@ class ChatActivity : R.layout.item_custom_outcoming_preview_message ) - messageHolders.registerContentType( - CONTENT_TYPE_TEMP, - TemporaryMessageViewHolder::class.java, - payload, - R.layout.item_temporary_message, - TemporaryMessageViewHolder::class.java, - payload, - R.layout.item_temporary_message, - this - ) - messageHolders.registerContentType( CONTENT_TYPE_SYSTEM_MESSAGE, SystemMessageViewHolder::class.java, @@ -3437,9 +3424,16 @@ class ChatActivity : private fun openMessageActionsDialog(iMessage: IMessage?) { val message = iMessage as ChatMessage - if (hasVisibleItems(message) && - !isSystemMessage(message) && - message.id != TEMPORARY_MESSAGE_ID_STRING + + if (message.isTemporary) { + TempMessageActionsDialog( + this, + message, + conversationUser, + currentConversation, + ).show() + } else if (hasVisibleItems(message) && + !isSystemMessage(message) ) { MessageActionsDialog( this, @@ -4011,30 +4005,6 @@ class ChatActivity : startACall(false, false) } - override fun editTemporaryMessage(id: Int, newMessage: String) { - // messageInputViewModel.editQueuedMessage(currentConversation!!.internalId, id, newMessage) - // adapter?.notifyDataSetChanged() // TODO optimize this - } - - override fun deleteTemporaryMessage(id: Int) { - // messageInputViewModel.removeFromQueue(currentConversation!!.internalId, id) - // var i = 0 - // val max = messageInputViewModel.messageQueueSizeFlow.value?.plus(1) - // for (item in adapter?.items!!) { - // if (i > max!! && max < 1) break - // if (item.item is ChatMessage && - // (item.item as ChatMessage).isTempMessage && - // (item.item as ChatMessage).tempMessageId == id - // ) { - // val index = adapter?.items!!.indexOf(item) - // adapter?.items!!.removeAt(index) - // adapter?.notifyItemRemoved(index) - // break - // } - // i++ - // } - } - private fun logConversationInfos(methodName: String) { Log.d(TAG, " |-----------------------------------------------") Log.d(TAG, " | method: $methodName") diff --git a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt index 5e8ee1bc1..33725b5cf 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -916,16 +916,23 @@ class MessageInputFragment : Fragment() { // FIXME Fix API checking with guests? val apiVersion: Int = ApiUtils.getChatApiVersion(chatActivity.spreedCapabilities, intArrayOf(1)) - chatActivity.messageInputViewModel.editChatMessage( - chatActivity.credentials!!, - ApiUtils.getUrlForChatMessage( - apiVersion, - chatActivity.conversationUser!!.baseUrl!!, - chatActivity.roomToken, - message.id - ), - editedMessageText - ) + if (message.isTemporary) { + chatActivity.messageInputViewModel.editTempChatMessage( + message, + editedMessageText + ) + } else { + chatActivity.messageInputViewModel.editChatMessage( + chatActivity.credentials!!, + ApiUtils.getUrlForChatMessage( + apiVersion, + chatActivity.conversationUser!!.baseUrl!!, + chatActivity.roomToken, + message.id + ), + editedMessageText + ) + } } private fun setEditUI(message: ChatMessage) { diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt index 630358040..6ad2599ef 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt @@ -14,6 +14,7 @@ import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow interface ChatMessageRepository : LifecycleAwareManager { @@ -97,4 +98,6 @@ interface ChatMessageRepository : LifecycleAwareManager { ): Flow> suspend fun editChatMessage(credentials: String, url: String, text: String): Flow> + + suspend fun editTempChatMessage(message: ChatMessage, editedMessageText: String): Flow } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt index 0e50ef18a..70f1139e6 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt @@ -115,9 +115,9 @@ data class ChatMessage( var openWhenDownloaded: Boolean = true, - var isTempMessage: Boolean = false, // TODO: replace logic from message drafts with logic from temp message sending + var isTemporary: Boolean = false, // TODO: replace logic from message drafts with logic from temp message sending - var tempMessageId: Int = -1, // TODO: replace logic from message drafts with logic from temp message sending + // var tempMessageId: Int = -1, // TODO: replace logic from message drafts with logic from temp message sending var referenceId: String? = null, diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index f08b278b4..25372edc8 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -13,10 +13,6 @@ import android.util.Log import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.data.ChatMessageRepository import com.nextcloud.talk.chat.data.model.ChatMessage -import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel -import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel.Companion -import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel.SendChatMessageErrorState -import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel.SendChatMessageSuccessState import com.nextcloud.talk.data.database.dao.ChatBlocksDao import com.nextcloud.talk.data.database.dao.ChatMessagesDao import com.nextcloud.talk.data.database.mappers.asEntity @@ -41,10 +37,13 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.retryWhen import kotlinx.coroutines.launch +import java.io.IOException import javax.inject.Inject class OfflineFirstChatRepository @Inject constructor( @@ -338,32 +337,6 @@ class OfflineFirstChatRepository @Inject constructor( } } - // TODO replace with WorkManager? - // private suspend fun tryToSendPendingMessages() { - // val tempMessages = chatDao.getTempMessagesForConversation(internalConversationId).first() - // - // tempMessages.forEach { - // Log.d(TAG, "Sending chat message ${it.message} another time!!") - // - // sendChatMessage( - // credentials, - // urlForChatting, - // it.message, - // it.actorDisplayName, - // it.parentMessageId?.toInt() ?: 0, - // false, - // it.referenceId ?: "" - // ).collect { result -> - // if (result.isSuccess) { - // Log.d(TAG, "success. received ref id: " + (result.getOrNull()?.referenceId ?: "none")) - // - // } else { - // Log.d(TAG, "fail. received ref id: " + (result.getOrNull()?.referenceId ?: "none")) - // } - // } - // } - // } - private suspend fun handleNewAndTempMessages( receivedChatMessages : List, lookIntoFuture: Boolean, @@ -829,35 +802,42 @@ class OfflineFirstChatRepository @Inject constructor( referenceId: String ): Flow> = flow { - try { - val response = network.sendChatMessage( - credentials, - url, - message, - displayName, - replyTo, - sendWithoutNotification, - referenceId - ) + val response = network.sendChatMessage( + credentials, + url, + message, + displayName, + replyTo, + sendWithoutNotification, + referenceId + ) - val chatMessageModel = response.ocs?.data?.asModel() + val chatMessageModel = response.ocs?.data?.asModel() - emit(Result.success(chatMessageModel)) - } catch (e: Exception) { - Log.e(TAG, "Error when sending message", e) + emit(Result.success(chatMessageModel)) + } + // .retryWhen { cause, attempt -> + // if (cause is IOException && attempt < 3) { + // delay(2000) + // return@retryWhen true + // } else { + // return@retryWhen false + // } + // } + .catch { e -> + Log.e(TAG, "Error when sending message", e) - val failedMessage = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first() - failedMessage.sendingFailed = true - chatDao.updateChatMessage(failedMessage) + val failedMessage = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first() + failedMessage.sendingFailed = true + chatDao.updateChatMessage(failedMessage) - // val failedMessageModel = failedMessage.asModel() - // _removeMessageFlow.emit(failedMessageModel) - // - // val tripleChatMessages = Triple(true, false, listOf(failedMessageModel)) - // _messageFlow.emit(tripleChatMessages) + val failedMessageModel = failedMessage.asModel() + _removeMessageFlow.emit(failedMessageModel) - emit(Result.failure(e)) - } + val tripleChatMessages = Triple(true, false, listOf(failedMessageModel)) + _messageFlow.emit(tripleChatMessages) + + emit(Result.failure(e)) } override suspend fun editChatMessage( @@ -878,6 +858,30 @@ class OfflineFirstChatRepository @Inject constructor( } } + override suspend fun editTempChatMessage( + message: ChatMessage, editedMessageText: String + ): Flow = + flow { + try { + val messageToEdit = chatDao.getChatMessageForConversation(internalConversationId, message.jsonMessageId + .toLong()).first() + messageToEdit.message = editedMessageText + chatDao.upsertChatMessage(messageToEdit) + + + val editedMessageModel = messageToEdit.asModel() + _removeMessageFlow.emit(editedMessageModel) + + val tripleChatMessages = Triple(true, false, listOf(editedMessageModel)) + _messageFlow.emit(tripleChatMessages) + + + emit(true) + } catch (e: Exception) { + emit(false) + } + } + override suspend fun addTemporaryMessage( message: CharSequence, displayName: String, @@ -917,7 +921,7 @@ class OfflineFirstChatRepository @Inject constructor( val entity = ChatMessageEntity( internalId = internalConversationId + "@_temp_" + currentTimeMillies, internalConversationId = internalConversationId, - id = currentTimeMillies, + id = currentTimeMillies, // TODO: currentTimeMillies fails as id because later on in the model it's not Long but Int!!!! message = message, deleted = false, token = conversationModel.token, diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 238a6f54d..1e2a89e43 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -219,6 +219,21 @@ class MessageInputViewModel @Inject constructor( } } + fun editTempChatMessage(message: ChatMessage, editedMessageText: String) { + viewModelScope.launch { + chatRepository.editTempChatMessage( + message, + editedMessageText + ).collect { result -> + if (true) { + // _editMessageViewState.value = EditMessageSuccessState(result) + } else { + // _editMessageViewState.value = EditMessageErrorState + } + } + } + } + fun reply(message: IMessage?) { _getReplyChatMessage.postValue(message) } diff --git a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt index b3179c149..7fb423767 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt @@ -32,6 +32,7 @@ interface ChatMessagesDao { SELECT * FROM ChatMessages WHERE internalConversationId = :internalConversationId + AND isTemporary = 0 ORDER BY timestamp DESC, id DESC """ ) @@ -78,10 +79,10 @@ interface ChatMessagesDao { @Query( value = """ DELETE FROM ChatMessages - WHERE id in (:messageIds) + WHERE internalId in (:internalIds) """ ) - fun deleteChatMessages(messageIds: List) + fun deleteChatMessages(internalIds: List) @Query( value = """ diff --git a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt index 1392a2920..5b95abe69 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt @@ -66,7 +66,7 @@ fun ChatMessageEntity.asModel() = lastEditTimestamp = lastEditTimestamp, isDeleted = deleted, referenceId = referenceId, - isTempMessage = isTemporary, + isTemporary = isTemporary, sendingFailed = sendingFailed, readStatus = setStatus(isTemporary, sendingFailed) ) diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt new file mode 100644 index 000000000..e9e73228a --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt @@ -0,0 +1,124 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2022 Marcel Hibbe + * SPDX-FileCopyrightText: 2022 Andy Scherzinger + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.talk.ui.dialog + +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.lifecycleScope +import autodagger.AutoInjector +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.nextcloud.talk.R +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.chat.ChatActivity +import com.nextcloud.talk.chat.data.model.ChatMessage +import com.nextcloud.talk.data.network.NetworkMonitor +import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.databinding.DialogMessageActionsBinding +import com.nextcloud.talk.databinding.DialogTempMessageActionsBinding +import com.nextcloud.talk.models.domain.ConversationModel +import com.nextcloud.talk.ui.theme.ViewThemeUtils +import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.DateUtils +import kotlinx.coroutines.launch +import javax.inject.Inject + +@AutoInjector(NextcloudTalkApplication::class) +class TempMessageActionsDialog( + private val chatActivity: ChatActivity, + private val message: ChatMessage, + private val user: User?, + private val currentConversation: ConversationModel? +) : BottomSheetDialog(chatActivity) { + + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + @Inject + lateinit var dateUtils: DateUtils + + @Inject + lateinit var networkMonitor: NetworkMonitor + + private lateinit var binding: DialogTempMessageActionsBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + NextcloudTalkApplication.sharedApplication?.componentApplication?.inject(this) + + binding = DialogTempMessageActionsBinding.inflate(layoutInflater) + setContentView(binding.root) + window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + + viewThemeUtils.material.colorBottomSheetBackground(binding.root) + viewThemeUtils.material.colorBottomSheetDragHandle(binding.bottomSheetDragHandle) + + initMenuItemCopy(!message.isDeleted) + val apiVersion = ApiUtils.getConversationApiVersion(user!!, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1)) + + + initMenuItems() + } + + private fun initMenuItems() { + this.lifecycleScope.launch { + initMenuEditMessage(true) + initMenuDeleteMessage(true) + } + } + + override fun onStart() { + super.onStart() + val bottomSheet = findViewById(R.id.design_bottom_sheet) + val behavior = BottomSheetBehavior.from(bottomSheet as View) + behavior.state = BottomSheetBehavior.STATE_COLLAPSED + } + + private fun initMenuDeleteMessage(visible: Boolean) { + if (visible) { + binding.menuDeleteMessage.setOnClickListener { + chatActivity.deleteMessage(message) + dismiss() + } + } + binding.menuDeleteMessage.visibility = getVisibility(visible) + } + + private fun initMenuEditMessage(visible: Boolean) { + binding.menuEditMessage.setOnClickListener { + chatActivity.messageInputViewModel.edit(message) + dismiss() + } + + binding.menuEditMessage.visibility = getVisibility(visible) + } + + private fun initMenuItemCopy(visible: Boolean) { + if (visible) { + binding.menuCopyMessage.setOnClickListener { + chatActivity.copyMessage(message) + dismiss() + } + } + + binding.menuCopyMessage.visibility = getVisibility(visible) + } + + private fun getVisibility(visible: Boolean): Int { + return if (visible) { + View.VISIBLE + } else { + View.GONE + } + } + + companion object { + private val TAG = TempMessageActionsDialog::class.java.simpleName + } +} diff --git a/app/src/main/res/layout/dialog_temp_message_actions.xml b/app/src/main/res/layout/dialog_temp_message_actions.xml new file mode 100644 index 000000000..c377fafc8 --- /dev/null +++ b/app/src/main/res/layout/dialog_temp_message_actions.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From f88b1f17f296add2010026bb202385cf53a62695 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 27 Dec 2024 09:15:48 +0100 Subject: [PATCH 13/40] fix after merge conflicts Signed-off-by: Marcel Hibbe --- .../nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt index a34337aa9..d808ac9a8 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt @@ -64,6 +64,6 @@ interface ChatNetworkDataSource { fun deleteChatMessage(credentials: String, url: String): Observable fun createRoom(credentials: String, url: String, map: Map): Observable fun setChatReadMarker(credentials: String, url: String, previousMessageId: Int): Observable - fun editChatMessage(credentials: String, url: String, text: String): Observable + suspend fun editChatMessage(credentials: String, url: String, text: String): ChatOverallSingleMessage suspend fun getOutOfOfficeStatusForUser(credentials: String, baseUrl: String, userId: String): UserAbsenceOverall } From 5ac130a020d2dd41a662d1d4be4285d866890ff8 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 27 Dec 2024 11:25:22 +0100 Subject: [PATCH 14/40] resend queued messages when connection gained Signed-off-by: Marcel Hibbe --- .../talk/chat/MessageInputFragment.kt | 11 +++++- .../talk/chat/data/ChatMessageRepository.kt | 5 +++ .../network/OfflineFirstChatRepository.kt | 36 +++++++++++++++++-- .../chat/viewmodels/MessageInputViewModel.kt | 12 +++++++ .../talk/extensions/LongFormatExtension.kt | 15 ++++++++ .../talk/utils/message/SendMessageUtils.kt | 13 +++++++ 6 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/talk/extensions/LongFormatExtension.kt diff --git a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt index 33725b5cf..89893d74b 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -198,7 +198,16 @@ class MessageInputFragment : Fragment() { wasOnline = !binding.fragmentConnectionLost.isShown val connectionGained = (!wasOnline && isOnline) Log.d(TAG, "isOnline: $isOnline\nwasOnline: $wasOnline\nconnectionGained: $connectionGained") - // handleMessageQueue(isOnline) + if (connectionGained) { + chatActivity.messageInputViewModel.sendTempMessages( + chatActivity.conversationUser!!.getCredentials(), + ApiUtils.getUrlForChat( + chatActivity.chatApiVersion, + chatActivity.conversationUser!!.baseUrl!!, + chatActivity.roomToken + ) + ) + } handleUI(isOnline, connectionGained) }.collect() } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt index 6ad2599ef..891db34a5 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt @@ -100,4 +100,9 @@ interface ChatMessageRepository : LifecycleAwareManager { suspend fun editChatMessage(credentials: String, url: String, text: String): Flow> suspend fun editTempChatMessage(message: ChatMessage, editedMessageText: String): Flow + + suspend fun sendTempChatMessages( + credentials: String, + url: String + ) } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 25372edc8..5bb113d96 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -21,12 +21,14 @@ import com.nextcloud.talk.data.database.model.ChatBlockEntity import com.nextcloud.talk.data.database.model.ChatMessageEntity 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.json.chat.ChatMessageJson import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew +import com.nextcloud.talk.utils.message.SendMessageUtils import com.nextcloud.talk.utils.preferences.AppPreferences import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers @@ -41,9 +43,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.retryWhen import kotlinx.coroutines.launch -import java.io.IOException import javax.inject.Inject class OfflineFirstChatRepository @Inject constructor( @@ -124,6 +124,8 @@ class OfflineFirstChatRepository @Inject constructor( override fun loadInitialMessages(withNetworkParams: Bundle): Job = scope.launch { + sendTempChatMessages(credentials, urlForChatting) + Log.d(TAG, "---- loadInitialMessages ------------") newXChatLastCommonRead = conversationModel.lastCommonReadMessage @@ -363,6 +365,7 @@ class OfflineFirstChatRepository @Inject constructor( // add the remaining temp messages to UI again val remainingTempMessages = chatDao.getTempMessagesForConversation(internalConversationId) .first() + .sortedBy { it.internalId } .map(ChatMessageEntity::asModel) val triple = Triple(true, false, remainingTempMessages) @@ -882,6 +885,31 @@ class OfflineFirstChatRepository @Inject constructor( } } + override suspend fun sendTempChatMessages( + credentials: String, + url: String + ) { + val tempMessages = chatDao.getTempMessagesForConversation(internalConversationId).first() + tempMessages.sortedBy { it.internalId }.onEach { + sendChatMessage( + credentials, + url, + it.message, + it.actorDisplayName, + it.parentMessageId?.toIntOrZero() ?: 0, + false, + it.referenceId.orEmpty() + ).collect { result -> + if (result.isSuccess) { + Log.d(TAG, "sent temp message") + } else { + Log.e(TAG, "Failed to send temp message") + } + } + } + + } + override suspend fun addTemporaryMessage( message: CharSequence, displayName: String, @@ -918,10 +946,12 @@ class OfflineFirstChatRepository @Inject constructor( val currentTimeMillies = System.currentTimeMillis() + val currentTimeWithoutYear = SendMessageUtils().removeYearFromTimestamp(currentTimeMillies) + val entity = ChatMessageEntity( internalId = internalConversationId + "@_temp_" + currentTimeMillies, internalConversationId = internalConversationId, - id = currentTimeMillies, // TODO: currentTimeMillies fails as id because later on in the model it's not Long but Int!!!! + id = currentTimeWithoutYear.toLong(), message = message, deleted = false, token = conversationModel.token, diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 1e2a89e43..4077224a2 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -203,6 +203,18 @@ class MessageInputViewModel @Inject constructor( } } + fun sendTempMessages( + credentials: String, + url: String, + ) { + viewModelScope.launch { + chatRepository.sendTempChatMessages( + credentials, + url + ) + } + } + fun editChatMessage(credentials: String, url: String, text: String) { viewModelScope.launch { chatRepository.editChatMessage( diff --git a/app/src/main/java/com/nextcloud/talk/extensions/LongFormatExtension.kt b/app/src/main/java/com/nextcloud/talk/extensions/LongFormatExtension.kt new file mode 100644 index 000000000..02cbe262f --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/extensions/LongFormatExtension.kt @@ -0,0 +1,15 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2024 Marcel Hibbe + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.talk.extensions + +fun Long.toIntOrZero(): Int { + return if (this >= Int.MIN_VALUE && this <= Int.MAX_VALUE) { + toInt() + } else { + 0 + } +} diff --git a/app/src/main/java/com/nextcloud/talk/utils/message/SendMessageUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/message/SendMessageUtils.kt index 29b9700ea..4d42bfa6a 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/message/SendMessageUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/message/SendMessageUtils.kt @@ -7,6 +7,7 @@ package com.nextcloud.talk.utils.message import java.security.MessageDigest +import java.util.Calendar import java.util.UUID class SendMessageUtils { @@ -17,6 +18,18 @@ class SendMessageUtils { return hashBytes.joinToString("") { "%02x".format(it) } } + @Suppress("MagicNumber") + fun removeYearFromTimestamp(timestampMillis: Long): Int { + val calendar = Calendar.getInstance().apply { timeInMillis = timestampMillis } + + val month = calendar.get(Calendar.MONTH) + val day = calendar.get(Calendar.DAY_OF_MONTH) + val hour = calendar.get(Calendar.HOUR_OF_DAY) + val minute = calendar.get(Calendar.MINUTE) + val second = calendar.get(Calendar.SECOND) + return (month * 1000000) + (day * 10000) + (hour * 100) + (minute * 10) + second + } + companion object { private val TAG = SendMessageUtils::class.java.simpleName } From e9f3863375b67e6d57caa8c891d730f0efb7173f Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 27 Dec 2024 12:00:50 +0100 Subject: [PATCH 15/40] delete temp messages Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/data/ChatMessageRepository.kt | 2 ++ .../talk/chat/data/network/OfflineFirstChatRepository.kt | 5 +++++ .../com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt | 6 ++++++ .../nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt | 2 +- 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt index 891db34a5..36345d5b9 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt @@ -105,4 +105,6 @@ interface ChatMessageRepository : LifecycleAwareManager { credentials: String, url: String ) + + suspend fun deleteTempMessage(chatMessage: ChatMessage) } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 5bb113d96..48fb5bae4 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -910,6 +910,11 @@ class OfflineFirstChatRepository @Inject constructor( } + override suspend fun deleteTempMessage(chatMessage: ChatMessage) { + chatDao.deleteTempChatMessages(internalConversationId, listOf(chatMessage.referenceId.orEmpty())) + _removeMessageFlow.emit(chatMessage) + } + override suspend fun addTemporaryMessage( message: CharSequence, displayName: String, diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt index 27f367556..ed0dbfaff 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt @@ -793,6 +793,12 @@ class ChatViewModel @Inject constructor( } } + fun deleteTempMessage(chatMessage: ChatMessage) { + viewModelScope.launch { + chatRepository.deleteTempMessage(chatMessage) + } + } + companion object { private val TAG = ChatViewModel::class.simpleName const val JOIN_ROOM_RETRY_COUNT: Long = 3 diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt index e9e73228a..9085762d2 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt @@ -83,7 +83,7 @@ class TempMessageActionsDialog( private fun initMenuDeleteMessage(visible: Boolean) { if (visible) { binding.menuDeleteMessage.setOnClickListener { - chatActivity.deleteMessage(message) + chatActivity.chatViewModel.deleteTempMessage(message) dismiss() } } From 2c397ad517a0a44d26003e191149559fb061718c Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 27 Dec 2024 12:40:32 +0100 Subject: [PATCH 16/40] add manual resend button Signed-off-by: Marcel Hibbe --- .../talk/chat/data/ChatMessageRepository.kt | 10 ++++++ .../network/OfflineFirstChatRepository.kt | 35 +++++++++++++++++-- .../talk/chat/viewmodels/ChatViewModel.kt | 24 +++++++++++++ .../ui/dialog/TempMessageActionsDialog.kt | 22 ++++++++++++ .../layout/dialog_temp_message_actions.xml | 33 +++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 6 files changed, 123 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt index 36345d5b9..7f22a80a6 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt @@ -90,6 +90,16 @@ interface ChatMessageRepository : LifecycleAwareManager { referenceId: String ): Flow> + suspend fun resendChatMessage( + credentials: String, + url: String, + message: String, + displayName: String, + replyTo: Int, + sendWithoutNotification: Boolean, + referenceId: String + ): Flow> + suspend fun addTemporaryMessage( message: CharSequence, displayName: String, diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 48fb5bae4..7d3ec20cd 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -124,8 +124,6 @@ class OfflineFirstChatRepository @Inject constructor( override fun loadInitialMessages(withNetworkParams: Bundle): Job = scope.launch { - sendTempChatMessages(credentials, urlForChatting) - Log.d(TAG, "---- loadInitialMessages ------------") newXChatLastCommonRead = conversationModel.lastCommonReadMessage @@ -197,6 +195,8 @@ class OfflineFirstChatRepository @Inject constructor( ) } + sendTempChatMessages(credentials, urlForChatting) + // delay is a dirty workaround to make sure messages are added to adapter on initial load before dealing // with them (otherwise there is a race condition). delay(DELAY_TO_ENSURE_MESSAGES_ARE_ADDED) @@ -843,6 +843,37 @@ class OfflineFirstChatRepository @Inject constructor( emit(Result.failure(e)) } + override suspend fun resendChatMessage( + credentials: String, + url: String, + message: String, + displayName: String, + replyTo: Int, + sendWithoutNotification: Boolean, + referenceId: String + ): Flow> { + val messageToResend = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first() + messageToResend.sendingFailed = false + chatDao.updateChatMessage(messageToResend) + + val messageToResendModel = messageToResend.asModel() + _removeMessageFlow.emit(messageToResendModel) + + val tripleChatMessages = Triple(true, false, listOf(messageToResendModel)) + _messageFlow.emit(tripleChatMessages) + + return sendChatMessage( + credentials, + url, + message, + displayName, + replyTo, + sendWithoutNotification, + referenceId + ) + } + + override suspend fun editChatMessage( credentials: String, url: String, diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt index ed0dbfaff..6a84780f9 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt @@ -24,6 +24,7 @@ import com.nextcloud.talk.chat.data.model.ChatMessage import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository import com.nextcloud.talk.data.user.model.User +import com.nextcloud.talk.extensions.toIntOrZero import com.nextcloud.talk.jobs.UploadAndShareFilesWorker import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.models.domain.ReactionAddedModel @@ -799,6 +800,29 @@ class ChatViewModel @Inject constructor( } } + fun resendMessage( + credentials: String, + urlForChat: String, + message: ChatMessage) { + viewModelScope.launch { + chatRepository.resendChatMessage( + credentials, + urlForChat, + message.message.orEmpty(), + message.actorDisplayName.orEmpty(), + message.parentMessageId?.toIntOrZero() ?: 0, + false, + message.referenceId.orEmpty() + ).collect { result -> + if (result.isSuccess) { + Log.d(TAG, "resend successful") + } else { + Log.e(TAG, "resend failed") + } + } + } + } + companion object { private val TAG = ChatViewModel::class.simpleName const val JOIN_ROOM_RETRY_COUNT: Long = 3 diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt index 9085762d2..cdebbfdf4 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt @@ -68,6 +68,7 @@ class TempMessageActionsDialog( private fun initMenuItems() { this.lifecycleScope.launch { + initResendMessage(true) initMenuEditMessage(true) initMenuDeleteMessage(true) } @@ -80,6 +81,27 @@ class TempMessageActionsDialog( behavior.state = BottomSheetBehavior.STATE_COLLAPSED } + private fun initResendMessage(visible: Boolean) { + if (visible) { + binding.menuResendMessage.setOnClickListener { + + + chatActivity.chatViewModel.resendMessage( + chatActivity.conversationUser!!.getCredentials(), + ApiUtils.getUrlForChat( + chatActivity.chatApiVersion, + chatActivity.conversationUser!!.baseUrl!!, + chatActivity.roomToken + ), + message + ) + + dismiss() + } + } + binding.menuResendMessage.visibility = getVisibility(visible) + } + private fun initMenuDeleteMessage(visible: Boolean) { if (visible) { binding.menuDeleteMessage.setOnClickListener { diff --git a/app/src/main/res/layout/dialog_temp_message_actions.xml b/app/src/main/res/layout/dialog_temp_message_actions.xml index c377fafc8..98d51ba81 100644 --- a/app/src/main/res/layout/dialog_temp_message_actions.xml +++ b/app/src/main/res/layout/dialog_temp_message_actions.xml @@ -27,6 +27,39 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + + + + + + %1$s is out of office and might not respond %1$s is out of office today Replacement: + Resend From 06c85095877a2125ebe2228bf6c2962fb46f8ec1 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 2 Jan 2025 12:41:51 +0100 Subject: [PATCH 17/40] add retry logic for sending messages, hide resend button when offline Signed-off-by: Marcel Hibbe --- .../network/OfflineFirstChatRepository.kt | 55 ++++++++++--------- .../ui/dialog/TempMessageActionsDialog.kt | 7 +-- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 7d3ec20cd..7748c74a8 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -43,7 +43,9 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.retryWhen import kotlinx.coroutines.launch +import java.io.IOException import javax.inject.Inject class OfflineFirstChatRepository @Inject constructor( @@ -181,7 +183,6 @@ class OfflineFirstChatRepository @Inject constructor( if (newestMessageIdFromDb.toInt() != 0) { val limit = getCappedMessagesAmountOfChatBlock(newestMessageIdFromDb) - val list = getMessagesBeforeAndEqual( newestMessageIdFromDb, internalConversationId, @@ -340,7 +341,7 @@ class OfflineFirstChatRepository @Inject constructor( } private suspend fun handleNewAndTempMessages( - receivedChatMessages : List, + receivedChatMessages: List, lookIntoFuture: Boolean, showUnreadMessagesMarker: Boolean ) { @@ -752,7 +753,6 @@ class OfflineFirstChatRepository @Inject constructor( it.map(ChatMessageEntity::asModel) }.first() - private suspend fun showMessagesBefore(internalConversationId: String, messageId: Long, limit: Int) { suspend fun getMessagesBefore( messageId: Long, @@ -819,29 +819,29 @@ class OfflineFirstChatRepository @Inject constructor( emit(Result.success(chatMessageModel)) } - // .retryWhen { cause, attempt -> - // if (cause is IOException && attempt < 3) { - // delay(2000) - // return@retryWhen true - // } else { - // return@retryWhen false - // } - // } - .catch { e -> - Log.e(TAG, "Error when sending message", e) + .retryWhen { cause, attempt -> + if (cause is IOException && attempt < SEND_MESSAGE_RETRY_ATTEMPTS) { + delay(SEND_MESSAGE_RETRY_DELAY) + return@retryWhen true + } else { + return@retryWhen false + } + } + .catch { e -> + Log.e(TAG, "Error when sending message", e) - val failedMessage = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first() - failedMessage.sendingFailed = true - chatDao.updateChatMessage(failedMessage) + val failedMessage = chatDao.getTempMessageForConversation(internalConversationId, referenceId).first() + failedMessage.sendingFailed = true + chatDao.updateChatMessage(failedMessage) - val failedMessageModel = failedMessage.asModel() - _removeMessageFlow.emit(failedMessageModel) + val failedMessageModel = failedMessage.asModel() + _removeMessageFlow.emit(failedMessageModel) - val tripleChatMessages = Triple(true, false, listOf(failedMessageModel)) - _messageFlow.emit(tripleChatMessages) + val tripleChatMessages = Triple(true, false, listOf(failedMessageModel)) + _messageFlow.emit(tripleChatMessages) - emit(Result.failure(e)) - } + emit(Result.failure(e)) + } override suspend fun resendChatMessage( credentials: String, @@ -873,7 +873,6 @@ class OfflineFirstChatRepository @Inject constructor( ) } - override suspend fun editChatMessage( credentials: String, url: String, @@ -897,12 +896,13 @@ class OfflineFirstChatRepository @Inject constructor( ): Flow = flow { try { - val messageToEdit = chatDao.getChatMessageForConversation(internalConversationId, message.jsonMessageId - .toLong()).first() + val messageToEdit = chatDao.getChatMessageForConversation( + internalConversationId, message.jsonMessageId + .toLong() + ).first() messageToEdit.message = editedMessageText chatDao.upsertChatMessage(messageToEdit) - val editedMessageModel = messageToEdit.asModel() _removeMessageFlow.emit(editedMessageModel) @@ -938,7 +938,6 @@ class OfflineFirstChatRepository @Inject constructor( } } } - } override suspend fun deleteTempMessage(chatMessage: ChatMessage) { @@ -1018,5 +1017,7 @@ class OfflineFirstChatRepository @Inject constructor( private const val DELAY_TO_ENSURE_MESSAGES_ARE_ADDED: Long = 100 private const val DEFAULT_MESSAGES_LIMIT = 100 private const val MILLIES = 1000 + private const val SEND_MESSAGE_RETRY_ATTEMPTS = 3 + private const val SEND_MESSAGE_RETRY_DELAY: Long = 2000 } } diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt index cdebbfdf4..a3403db46 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt @@ -20,12 +20,12 @@ import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.data.model.ChatMessage import com.nextcloud.talk.data.network.NetworkMonitor import com.nextcloud.talk.data.user.model.User -import com.nextcloud.talk.databinding.DialogMessageActionsBinding import com.nextcloud.talk.databinding.DialogTempMessageActionsBinding import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DateUtils +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import javax.inject.Inject @@ -68,7 +68,7 @@ class TempMessageActionsDialog( private fun initMenuItems() { this.lifecycleScope.launch { - initResendMessage(true) + initResendMessage(networkMonitor.isOnline.first()) initMenuEditMessage(true) initMenuDeleteMessage(true) } @@ -84,8 +84,6 @@ class TempMessageActionsDialog( private fun initResendMessage(visible: Boolean) { if (visible) { binding.menuResendMessage.setOnClickListener { - - chatActivity.chatViewModel.resendMessage( chatActivity.conversationUser!!.getCredentials(), ApiUtils.getUrlForChat( @@ -95,7 +93,6 @@ class TempMessageActionsDialog( ), message ) - dismiss() } } From 656be3ffcea2da103d5b7828b2de3de987bc7972 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 2 Jan 2025 13:18:17 +0100 Subject: [PATCH 18/40] revert ReadStatus SENDING and FAILED Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/chat/ChatActivity.kt | 5 ----- .../data/database/mappers/ChatMessageMapUtils.kt | 12 +----------- .../nextcloud/talk/models/json/chat/ReadStatus.kt | 4 +--- 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index cfe4b36e9..9b521c3b0 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -3857,8 +3857,6 @@ class ChatActivity : CONTENT_TYPE_SYSTEM_MESSAGE -> !TextUtils.isEmpty(message.systemMessage) CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> message.id == UNREAD_MESSAGES_MARKER_ID.toString() CONTENT_TYPE_CALL_STARTED -> message.id == "-2" - // CONTENT_TYPE_TEMP -> message.id == TEMPORARY_MESSAGE_ID_STRING - // CONTENT_TYPE_TEMP -> message.readStatus == ReadStatus.FAILED CONTENT_TYPE_DECK_CARD -> message.isDeckCard() else -> false @@ -4053,7 +4051,6 @@ class ChatActivity : private const val CONTENT_TYPE_POLL: Byte = 6 private const val CONTENT_TYPE_LINK_PREVIEW: Byte = 7 private const val CONTENT_TYPE_DECK_CARD: Byte = 8 - private const val CONTENT_TYPE_TEMP: Byte = 9 private const val UNREAD_MESSAGES_MARKER_ID = -1 private const val CALL_STARTED_ID = -2 private const val GET_ROOM_INFO_DELAY_NORMAL: Long = 30000 @@ -4093,8 +4090,6 @@ class ChatActivity : private const val RESUME_AUDIO_TAG = "RESUME_AUDIO_TAG" private const val DELAY_TO_SHOW_PROGRESS_BAR = 1000L private const val FIVE_MINUTES_IN_SECONDS: Long = 300 - private const val TEMPORARY_MESSAGE_ID_INT: Int = -3 - private const val TEMPORARY_MESSAGE_ID_STRING: String = "-3" private const val ROOM_TYPE_ONE_TO_ONE = "1" private const val ACTOR_TYPE = "users" const val CONVERSATION_INTERNAL_ID = "CONVERSATION_INTERNAL_ID" diff --git a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt index 5b95abe69..8e3985ad0 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt @@ -68,19 +68,9 @@ fun ChatMessageEntity.asModel() = referenceId = referenceId, isTemporary = isTemporary, sendingFailed = sendingFailed, - readStatus = setStatus(isTemporary, sendingFailed) + readStatus = ReadStatus.NONE ) -fun setStatus(isTemporary: Boolean, sendingFailed: Boolean): ReadStatus { - return if (sendingFailed) { - ReadStatus.FAILED - } else if (isTemporary) { - ReadStatus.SENDING - } else { - ReadStatus.NONE - } -} - fun ChatMessageJson.asModel() = ChatMessage( jsonMessageId = id.toInt(), diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ReadStatus.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ReadStatus.kt index 1441dd521..40a1e283c 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ReadStatus.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ReadStatus.kt @@ -9,7 +9,5 @@ package com.nextcloud.talk.models.json.chat enum class ReadStatus { NONE, SENT, - READ, - SENDING, - FAILED + READ } From f62941f6aa45fdf1e53874a32f190102c7861bd4 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 2 Jan 2025 13:59:33 +0100 Subject: [PATCH 19/40] simplify "sent messages are queued" hint Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 29 ------ .../talk/chat/MessageInputFragment.kt | 25 ----- .../chat/viewmodels/MessageInputViewModel.kt | 94 ------------------- .../utils/preferences/AppPreferencesImpl.kt | 70 -------------- app/src/main/res/values/strings.xml | 1 - 5 files changed, 219 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 9b521c3b0..5922e4d13 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -581,35 +581,6 @@ class ChatActivity : private fun initObservers() { Log.d(TAG, "initObservers Called") - // messageInputViewModel.messageQueueFlow.observe(this) { list -> - // list.forEachIndexed { _, qMsg -> - // val temporaryChatMessage = ChatMessage() - // temporaryChatMessage.jsonMessageId = TEMPORARY_MESSAGE_ID_INT - // temporaryChatMessage.actorId = TEMPORARY_MESSAGE_ID_STRING - // temporaryChatMessage.timestamp = System.currentTimeMillis() / ONE_SECOND_IN_MILLIS - // temporaryChatMessage.message = qMsg.message.toString() - // temporaryChatMessage.tempMessageId = qMsg.id - // temporaryChatMessage.isTempMessage = true - // temporaryChatMessage.parentMessageId = qMsg.replyTo!!.toLong() - // val pos = adapter?.getMessagePositionById(qMsg.replyTo.toString()) - // adapter?.addToStart(temporaryChatMessage, true) - // adapter?.notifyDataSetChanged() - // } - // } - - messageInputViewModel.messageQueueSizeFlow.observe(this) { size -> - // if (size == 0) { - // var i = 0 - // var pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING) - // while (pos != null && pos > -1) { - // adapter?.items?.removeAt(pos) - // i++ - // pos = adapter?.getMessagePositionById(TEMPORARY_MESSAGE_ID_STRING) - // } - // adapter?.notifyDataSetChanged() - // } - } - this.lifecycleScope.launch { chatViewModel.getConversationFlow .onEach { conversationModel -> diff --git a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt index 89893d74b..9c4c02e27 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -212,14 +212,6 @@ class MessageInputFragment : Fragment() { }.collect() } - chatActivity.messageInputViewModel.messageQueueSizeFlow.observe(viewLifecycleOwner) { size -> - if (size > 0) { - binding.fragmentConnectionLost.text = getString(R.string.connection_lost_queued, size) - } else { - binding.fragmentConnectionLost.text = getString(R.string.connection_lost_sent_messages_are_queued) - } - } - chatActivity.messageInputViewModel.callStartedFlow.observe(viewLifecycleOwner) { val (message, show) = it if (show) { @@ -300,23 +292,6 @@ class MessageInputFragment : Fragment() { } } - // private fun handleMessageQueue(isOnline: Boolean) { - // if (isOnline) { - // chatActivity.messageInputViewModel.switchToMessageQueue(false) - // chatActivity.messageInputViewModel.sendAndEmptyMessageQueue( - // conversationInternalId, - // chatActivity.conversationUser!!.getCredentials(), - // ApiUtils.getUrlForChat( - // chatActivity.chatApiVersion, - // chatActivity.conversationUser!!.baseUrl!!, - // chatActivity.roomToken - // ) - // ) - // } else { - // chatActivity.messageInputViewModel.switchToMessageQueue(true) - // } - // } - private fun restoreState() { if (binding.fragmentMessageInputView.inputEditText.text.isEmpty()) { requireContext().getSharedPreferences(chatActivity.localClassName, AppCompatActivity.MODE_PRIVATE).apply { diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 4077224a2..49216d7d7 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -55,17 +55,6 @@ class MessageInputViewModel @Inject constructor( chatRepository = chatMessageRepository } - // data class QueuedMessage( - // val id: Int, - // var message: CharSequence? = null, - // val displayName: String? = null, - // val replyTo: Int? = null, - // val sendWithoutNotification: Boolean? = null - // ) - - // private var isQueueing: Boolean = false - // private var messageQueue: MutableList = mutableListOf() - override fun onResume(owner: LifecycleOwner) { super.onResume(owner) currentLifeCycleFlag = LifeCycleFlag.RESUMED @@ -117,7 +106,6 @@ class MessageInputViewModel @Inject constructor( private val _sendChatMessageViewState: MutableLiveData = MutableLiveData(SendChatMessageStartState) val sendChatMessageViewState: LiveData get() = _sendChatMessageViewState - object EditMessageStartState : ViewState object EditMessageErrorState : ViewState class EditMessageSuccessState(val messageEdited: ChatOverallSingleMessage) : ViewState @@ -129,14 +117,6 @@ class MessageInputViewModel @Inject constructor( val isVoicePreviewPlaying: LiveData get() = _isVoicePreviewPlaying - private val _messageQueueSizeFlow = MutableStateFlow(666) - val messageQueueSizeFlow: LiveData - get() = _messageQueueSizeFlow.asLiveData() - - // private val _messageQueueFlow: MutableLiveData> = MutableLiveData() - // val messageQueueFlow: LiveData> - // get() = _messageQueueFlow - private val _callStartedFlow: MutableLiveData> = MutableLiveData() val callStartedFlow: LiveData> get() = _callStartedFlow @@ -171,17 +151,6 @@ class MessageInputViewModel @Inject constructor( } } - // if (isQueueing) { - // val tempID = System.currentTimeMillis().toInt() - // val qMsg = QueuedMessage(tempID, message, displayName, replyTo, sendWithoutNotification) - // messageQueue = appPreferences.getMessageQueue(internalId) - // messageQueue.add(qMsg) - // appPreferences.saveMessageQueue(internalId, messageQueue) - // _messageQueueSizeFlow.update { messageQueue.size } - // _messageQueueFlow.postValue(listOf(qMsg)) - // return - // } - viewModelScope.launch { chatRepository.sendChatMessage( credentials, @@ -295,69 +264,6 @@ class MessageInputViewModel @Inject constructor( _getRecordingTime.postValue(time) } - // fun sendAndEmptyMessageQueue(internalId: String, credentials: String, url: String) { - // if (isQueueing) return - // messageQueue.clear() - // - // val queue = appPreferences.getMessageQueue(internalId) - // appPreferences.saveMessageQueue(internalId, null) // empties the queue - // while (queue.size > 0) { - // val msg = queue.removeAt(0) - // sendChatMessage( - // internalId, - // credentials, - // url, - // msg.message!!, - // msg.displayName!!, - // msg.replyTo!!, - // msg.sendWithoutNotification!! - // ) - // sleep(DELAY_BETWEEN_QUEUED_MESSAGES) - // } - // _messageQueueSizeFlow.tryEmit(0) - // } - // - // fun getTempMessagesFromMessageQueue(internalId: String) { - // val queue = appPreferences.getMessageQueue(internalId) - // val list = mutableListOf() - // for (msg in queue) { - // list.add(msg) - // } - // _messageQueueFlow.postValue(list) - // } - // - // fun switchToMessageQueue(shouldQueue: Boolean) { - // isQueueing = shouldQueue - // } - // - // fun restoreMessageQueue(internalId: String) { - // messageQueue = appPreferences.getMessageQueue(internalId) - // _messageQueueSizeFlow.tryEmit(messageQueue.size) - // } - // - // fun removeFromQueue(internalId: String, id: Int) { - // val queue = appPreferences.getMessageQueue(internalId) - // for (qMsg in queue) { - // if (qMsg.id == id) { - // queue.remove(qMsg) - // break - // } - // } - // appPreferences.saveMessageQueue(internalId, queue) - // _messageQueueSizeFlow.tryEmit(queue.size) - // } - // - // fun editQueuedMessage(internalId: String, id: Int, newMessage: String) { - // val queue = appPreferences.getMessageQueue(internalId) - // for (qMsg in queue) { - // if (qMsg.id == id) { - // qMsg.message = newMessage - // break - // } - // } - // appPreferences.saveMessageQueue(internalId, queue) - // } - fun showCallStartedIndicator(recent: ChatMessage, show: Boolean) { _callStartedFlow.postValue(Pair(recent, show)) } diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt index 742b27c26..39bfa5460 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt @@ -501,76 +501,6 @@ class AppPreferencesImpl(val context: Context) : AppPreferences { return if (lastReadId.isNotEmpty()) lastReadId.toInt() else defaultValue } - // override fun saveMessageQueue( - // internalConversationId: String, - // queue: MutableList? - // ) { - // runBlocking { - // async { - // var queueStr = "" - // queue?.let { - // for (msg in queue) { - // val msgStr = "${msg.id},${msg.message},${msg.replyTo},${msg.displayName},${ - // msg - // .sendWithoutNotification - // }^" - // queueStr += msgStr - // } - // } - // writeString(internalConversationId + MESSAGE_QUEUE, queueStr) - // } - // } - // } - // - // @Suppress("Detekt.TooGenericExceptionCaught") - // override fun getMessageQueue(internalConversationId: String): MutableList { - // val queueStr = - // runBlocking { async { readString(internalConversationId + MESSAGE_QUEUE).first() } }.getCompleted() - // - // val queue: MutableList = mutableListOf() - // if (queueStr.isEmpty()) return queue - // - // for (msgStr in queueStr.split("^")) { - // try { - // if (msgStr.isNotEmpty()) { - // val msgArray = msgStr.split(",") - // val id = msgArray[ID].toInt() - // val message = msgArray[MESSAGE_INDEX] - // val replyTo = msgArray[REPLY_TO_INDEX].toInt() - // val displayName = msgArray[DISPLAY_NAME_INDEX] - // val silent = msgArray[SILENT_INDEX].toBoolean() - // - // val qMsg = MessageInputViewModel.QueuedMessage(id, message, displayName, replyTo, silent) - // queue.add(qMsg) - // } - // } catch (e: IndexOutOfBoundsException) { - // Log.e(TAG, "Message string: $msgStr\n Queue String: $queueStr \n$e") - // } - // } - // - // return queue - // } - - // override fun deleteAllMessageQueuesFor(userId: String) { - // runBlocking { - // async { - // val keyList = mutableListOf>() - // val preferencesMap = context.dataStore.data.first().asMap() - // for (preference in preferencesMap) { - // if (preference.key.name.contains("$userId@")) { - // keyList.add(preference.key) - // } - // } - // - // for (key in keyList) { - // context.dataStore.edit { - // it.remove(key) - // } - // } - // } - // } - // } - override fun saveVoiceMessagePlaybackSpeedPreferences(speeds: Map) { Json.encodeToString(speeds).let { runBlocking { async { writeString(VOICE_MESSAGE_PLAYBACK_SPEEDS, it) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 200147998..89ea1fa50 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -820,7 +820,6 @@ How to translate with transifex: Show banned participants Bans list Connection lost - Sent messages are queued - Connection lost - %1$d are queued Connection established Message deleted by you Unban From cca4e69cea1c0501f5fd7d7d58f497bbaf883a42 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 2 Jan 2025 15:07:53 +0100 Subject: [PATCH 20/40] Skip to send message when device is offline Signed-off-by: Marcel Hibbe --- .../chat/data/network/OfflineFirstChatRepository.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 7748c74a8..2c5810fbc 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -803,8 +803,14 @@ class OfflineFirstChatRepository @Inject constructor( replyTo: Int, sendWithoutNotification: Boolean, referenceId: String - ): Flow> = - flow { + ): Flow> { + if (!monitor.isOnline.first()) { + return flow { + emit(Result.failure(IOException("Skipped to send message as device is offline"))) + } + } + + return flow { val response = network.sendChatMessage( credentials, url, @@ -842,6 +848,7 @@ class OfflineFirstChatRepository @Inject constructor( emit(Result.failure(e)) } + } override suspend fun resendChatMessage( credentials: String, @@ -932,7 +939,7 @@ class OfflineFirstChatRepository @Inject constructor( it.referenceId.orEmpty() ).collect { result -> if (result.isSuccess) { - Log.d(TAG, "sent temp message") + Log.d(TAG, "Sent temp message") } else { Log.e(TAG, "Failed to send temp message") } From 7d9c2fdd30d01c83af965916e6f77f734da1a9e6 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 2 Jan 2025 15:19:10 +0100 Subject: [PATCH 21/40] simplify to refresh message Signed-off-by: Marcel Hibbe --- .../data/network/OfflineFirstChatRepository.kt | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 2c5810fbc..2c456c051 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -841,10 +841,7 @@ class OfflineFirstChatRepository @Inject constructor( chatDao.updateChatMessage(failedMessage) val failedMessageModel = failedMessage.asModel() - _removeMessageFlow.emit(failedMessageModel) - - val tripleChatMessages = Triple(true, false, listOf(failedMessageModel)) - _messageFlow.emit(tripleChatMessages) + _updateMessageFlow.emit(failedMessageModel) emit(Result.failure(e)) } @@ -864,10 +861,7 @@ class OfflineFirstChatRepository @Inject constructor( chatDao.updateChatMessage(messageToResend) val messageToResendModel = messageToResend.asModel() - _removeMessageFlow.emit(messageToResendModel) - - val tripleChatMessages = Triple(true, false, listOf(messageToResendModel)) - _messageFlow.emit(tripleChatMessages) + _updateMessageFlow.emit(messageToResendModel) return sendChatMessage( credentials, @@ -911,12 +905,7 @@ class OfflineFirstChatRepository @Inject constructor( chatDao.upsertChatMessage(messageToEdit) val editedMessageModel = messageToEdit.asModel() - _removeMessageFlow.emit(editedMessageModel) - - val tripleChatMessages = Triple(true, false, listOf(editedMessageModel)) - _messageFlow.emit(tripleChatMessages) - - + _updateMessageFlow.emit(editedMessageModel) emit(true) } catch (e: Exception) { emit(false) From a5049fbb1fd1f23b45da94dadf737c8c69a50a87 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Thu, 2 Jan 2025 15:44:01 +0100 Subject: [PATCH 22/40] adjust some pixels to avoid jump of message list Signed-off-by: Marcel Hibbe --- .../item_custom_outcoming_text_message.xml | 42 ++----------------- 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/app/src/main/res/layout/item_custom_outcoming_text_message.xml b/app/src/main/res/layout/item_custom_outcoming_text_message.xml index de86bd000..a124f1931 100644 --- a/app/src/main/res/layout/item_custom_outcoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_outcoming_text_message.xml @@ -16,42 +16,6 @@ android:layout_marginRight="16dp" android:layout_marginBottom="2dp"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Date: Thu, 2 Jan 2025 15:49:12 +0100 Subject: [PATCH 23/40] delete roomId comments Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 5922e4d13..9378ab457 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -316,7 +316,6 @@ class ChatActivity : var startCallFromNotification: Boolean = false var startCallFromRoomSwitch: Boolean = false - // lateinit var roomId: String var voiceOnly: Boolean = true private lateinit var path: String @@ -522,7 +521,6 @@ class ChatActivity : private fun handleIntent(intent: Intent) { val extras: Bundle? = intent.extras - // roomId = extras?.getString(KEY_ROOM_ID).orEmpty() roomToken = extras?.getString(KEY_ROOM_TOKEN).orEmpty() sharedText = extras?.getString(BundleKeys.KEY_SHARED_TEXT).orEmpty() @@ -712,7 +710,6 @@ class ChatActivity : sessionIdAfterRoomJoined = currentConversation!!.sessionId ApplicationWideCurrentRoomHolder.getInstance().session = currentConversation!!.sessionId - // ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = currentConversation!!.roomId ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken = currentConversation!!.token ApplicationWideCurrentRoomHolder.getInstance().userInRoom = conversationUser @@ -782,19 +779,6 @@ class ChatActivity : is MessageInputViewModel.SendChatMessageErrorState -> { binding.messagesListView.smoothScrollToPosition(0) - - // if (state.e is HttpException) { - // val code = state.e.code() - // if (code.toString().startsWith("2")) { - // myFirstMessage = state.message - // - // if (binding.unreadMessagesPopup.isShown) { - // binding.unreadMessagesPopup.visibility = View.GONE - // } - // - // binding.messagesListView.smoothScrollToPosition(0) - // } - // } } else -> {} @@ -831,7 +815,6 @@ class ChatActivity : is ChatViewModel.CreateRoomSuccessState -> { val bundle = Bundle() bundle.putString(KEY_ROOM_TOKEN, state.roomOverall.ocs!!.data!!.token) - // bundle.putString(KEY_ROOM_ID, state.roomOverall.ocs!!.data!!.roomId) leaveRoom { val chatIntent = Intent(context, ChatActivity::class.java) @@ -2700,7 +2683,6 @@ class ChatActivity : ) { sessionIdAfterRoomJoined = ApplicationWideCurrentRoomHolder.getInstance().session - // ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = roomId ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken = roomToken ApplicationWideCurrentRoomHolder.getInstance().userInRoom = conversationUser } @@ -3326,7 +3308,6 @@ class ChatActivity : currentConversation?.let { val bundle = Bundle() bundle.putString(KEY_ROOM_TOKEN, roomToken) - // bundle.putString(KEY_ROOM_ID, roomId) bundle.putString(BundleKeys.KEY_CONVERSATION_PASSWORD, roomPassword) bundle.putString(BundleKeys.KEY_MODIFIED_BASE_URL, conversationUser?.baseUrl!!) bundle.putString(KEY_CONVERSATION_NAME, it.displayName) From 29362fab4b778a55ca3ecb61deaa252b5c9a2d56 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 3 Jan 2025 10:50:18 +0100 Subject: [PATCH 24/40] add logging for unread messages popup bug Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 9378ab457..5f3dbbd32 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -1042,8 +1042,10 @@ class ChatActivity : is ChatViewModel.OutOfOfficeUIState.Error -> { Log.e(TAG, "Error fetching/ no user absence data", uiState.exception) } + ChatViewModel.OutOfOfficeUIState.None -> { } + is ChatViewModel.OutOfOfficeUIState.Success -> { binding.outOfOfficeContainer.visibility = View.VISIBLE @@ -2922,7 +2924,23 @@ class ChatActivity : } } - private fun isScrolledToBottom() = layoutManager?.findFirstVisibleItemPosition() == 0 + private fun isScrolledToBottom(): Boolean { + val position = layoutManager?.findFirstVisibleItemPosition() + Log.d(TAG, "first visible item position is :" + position) + + if (position == -1) { + Log.d(TAG, "position is -1") + } else if (position != null) { + val item = adapter?.items?.get(position)?.item + if (item is ChatMessage) { + Log.d(TAG, "first visible item message is :" + item.message) + } else if (item is Date) { + Log.d(TAG, "first visible item time is :" + item.time) + } + } + + return layoutManager?.findFirstVisibleItemPosition() == 0 + } private fun setUnreadMessageMarker(chatMessageList: List) { if (chatMessageList.isNotEmpty()) { @@ -3272,7 +3290,7 @@ class ChatActivity : private fun isInfoMessageAboutDeletion(currentMessage: MutableMap.MutableEntry): Boolean = currentMessage.value.parentMessageId != null && currentMessage.value.systemMessageType == ChatMessage - .SystemMessageType.MESSAGE_DELETED + .SystemMessageType.MESSAGE_DELETED private fun isReactionsMessage(currentMessage: MutableMap.MutableEntry): Boolean = currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION || @@ -3282,7 +3300,7 @@ class ChatActivity : private fun isEditMessage(currentMessage: MutableMap.MutableEntry): Boolean = currentMessage.value.parentMessageId != null && currentMessage.value.systemMessageType == ChatMessage - .SystemMessageType.MESSAGE_EDITED + .SystemMessageType.MESSAGE_EDITED private fun isPollVotedMessage(currentMessage: MutableMap.MutableEntry): Boolean = currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.POLL_VOTED @@ -3592,7 +3610,7 @@ class ChatActivity : val lon = data["longitude"]!! metaData = "{\"type\":\"geo-location\",\"id\":\"geo:$lat,$lon\",\"latitude\":\"$lat\"," + - "\"longitude\":\"$lon\",\"name\":\"$name\"}" + "\"longitude\":\"$lon\",\"name\":\"$name\"}" } shareToNotes(shareUri, roomToken, message, objectId, metaData) From 0356f5ac3c0c7a9126db7d324c8ab6cbcdccc571 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 3 Jan 2025 14:49:08 +0100 Subject: [PATCH 25/40] fix to not accidentally show unread messages popup reason was that the UI was not yet loaded but isScrolledToBottom was already called, so findFirstVisibleItemPosition returned -1. Fix for now is to return true for isScrolledToBottom when position is -1 They does not solve the root cause for now. It should be made sure the code is not executed until UI is ready. A quick try with repeatOnLifecycle(Lifecycle.State.STARTED) when collecting getMessageFlow did not help. Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/chat/ChatActivity.kt | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index 5f3dbbd32..b34481076 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -65,8 +65,10 @@ import androidx.core.text.bold import androidx.emoji2.text.EmojiCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.commit +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -2926,17 +2928,10 @@ class ChatActivity : private fun isScrolledToBottom(): Boolean { val position = layoutManager?.findFirstVisibleItemPosition() - Log.d(TAG, "first visible item position is :" + position) - if (position == -1) { - Log.d(TAG, "position is -1") - } else if (position != null) { - val item = adapter?.items?.get(position)?.item - if (item is ChatMessage) { - Log.d(TAG, "first visible item message is :" + item.message) - } else if (item is Date) { - Log.d(TAG, "first visible item time is :" + item.time) - } + Log.w(TAG, "FirstVisibleItemPosition was -1 but true is returned for isScrolledToBottom(). This can " + + "happen when the UI is not yet ready") + return true } return layoutManager?.findFirstVisibleItemPosition() == 0 From 8f1f22fd54b2d3100530e1c6d35a8351da2e15e6 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 3 Jan 2025 17:39:00 +0100 Subject: [PATCH 26/40] Fix duplicate "Today"-bug If user sent a message as a first message in today's chat, the temp message will be deleted when messages are retrieved from server, but also the date has to be deleted as it will be added again when the chat messages are added from server. Otherwise date "Today" would be shown twice. Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index b34481076..a3be754ee 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -65,10 +65,8 @@ import androidx.core.text.bold import androidx.emoji2.text.EmojiCompat import androidx.fragment.app.DialogFragment import androidx.fragment.app.commit -import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -1140,11 +1138,22 @@ class ChatActivity : } // do not use adapter.deleteById() as it seems to contain a bug! Use this method instead! + @Suppress("MagicNumber") private fun removeMessageById(idToDelete: String) { - val index = adapter?.getMessagePositionById(idToDelete) - if (index != null && index != -1) { - adapter?.items?.removeAt(index) - adapter?.notifyItemRemoved(index) + val indexToDelete = adapter?.getMessagePositionById(idToDelete) + if (indexToDelete != null && indexToDelete != UNREAD_MESSAGES_MARKER_ID) { + + // If user sent a message as a first message in todays chat, the temp message will be deleted when + // messages are retrieved from server, but also the date has to be deleted as it will be added again + // when the chat messages are added from server. Otherwise date "Today" would be shown twice. + if (indexToDelete == 0 && (adapter?.items?.get(1))?.item is Date) { + adapter?.items?.removeAt(0) + adapter?.items?.removeAt(0) + adapter?.notifyItemRangeRemoved(indexToDelete,1) + } else { + adapter?.items?.removeAt(indexToDelete) + adapter?.notifyItemRemoved(indexToDelete) + } } } From ab007fc4441d6b3aba93d1909a2aad22620b9a0d Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 3 Jan 2025 18:19:33 +0100 Subject: [PATCH 27/40] resolve codacy/ktlint warnings Signed-off-by: Marcel Hibbe --- .../OutcomingTextMessageViewHolder.kt | 8 +--- .../com/nextcloud/talk/api/NcApiCoroutines.kt | 1 + .../com/nextcloud/talk/chat/ChatActivity.kt | 22 ++++++----- .../talk/chat/MessageInputFragment.kt | 1 - .../talk/chat/data/ChatMessageRepository.kt | 8 ++-- .../talk/chat/data/model/ChatMessage.kt | 4 +- .../network/OfflineFirstChatRepository.kt | 17 ++++----- .../chat/data/network/RetrofitChatNetwork.kt | 2 +- .../talk/chat/viewmodels/ChatViewModel.kt | 31 +--------------- .../chat/viewmodels/MessageInputViewModel.kt | 16 ++------ .../talk/dagger/modules/RepositoryModule.kt | 1 - .../data/database/model/ChatMessageEntity.kt | 2 +- .../talk/receivers/DirectReplyReceiver.kt | 2 +- .../ui/dialog/TempMessageActionsDialog.kt | 37 ++++++------------- .../utils/preferences/AppPreferencesImpl.kt | 1 - 15 files changed, 48 insertions(+), 105 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 6e970b799..086038720 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -119,7 +119,6 @@ class OutcomingTextMessageViewHolder(itemView: View) : binding.messageQuote.quotedChatMessageView.visibility = View.GONE } - CoroutineScope(Dispatchers.Main).launch { if (message.isTemporary && !networkMonitor.isOnline.first()) { updateStatus( @@ -134,12 +133,11 @@ class OutcomingTextMessageViewHolder(itemView: View) : binding.bubble.setOnClickListener { commonMessageInterface.onOpenMessageActionsDialog(message) } - } else if (message.isTemporary) { showSendingSpinner() - } else if(message.readStatus == ReadStatus.READ) { + } else if (message.readStatus == ReadStatus.READ) { updateStatus(R.drawable.ic_check_all, context.resources?.getString(R.string.nc_message_read)) - } else if(message.readStatus == ReadStatus.SENT) { + } else if (message.readStatus == ReadStatus.SENT) { updateStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent)) } } @@ -176,8 +174,6 @@ class OutcomingTextMessageViewHolder(itemView: View) : viewThemeUtils.material.colorProgressBar(binding.sendingProgress) } - - private fun longClickOnReaction(chatMessage: ChatMessage) { commonMessageInterface.onLongClickReactions(chatMessage) } diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt index a1b027727..88ebffd82 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt +++ b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt @@ -123,6 +123,7 @@ interface NcApiCoroutines { @DELETE suspend fun unarchiveConversation(@Header("Authorization") authorization: String, @Url url: String): GenericOverall + @Suppress("LongParameterList") @FormUrlEncoded @POST suspend fun sendChatMessage( diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index a3be754ee..b46cc5227 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -1142,14 +1142,13 @@ class ChatActivity : private fun removeMessageById(idToDelete: String) { val indexToDelete = adapter?.getMessagePositionById(idToDelete) if (indexToDelete != null && indexToDelete != UNREAD_MESSAGES_MARKER_ID) { - // If user sent a message as a first message in todays chat, the temp message will be deleted when // messages are retrieved from server, but also the date has to be deleted as it will be added again // when the chat messages are added from server. Otherwise date "Today" would be shown twice. if (indexToDelete == 0 && (adapter?.items?.get(1))?.item is Date) { adapter?.items?.removeAt(0) adapter?.items?.removeAt(0) - adapter?.notifyItemRangeRemoved(indexToDelete,1) + adapter?.notifyItemRangeRemoved(indexToDelete, 1) } else { adapter?.items?.removeAt(indexToDelete) adapter?.notifyItemRemoved(indexToDelete) @@ -1170,7 +1169,7 @@ class ChatActivity : cancelNotificationsForCurrentConversation() - chatViewModel.getRoom(conversationUser!!, roomToken) + chatViewModel.getRoom(roomToken) actionBar?.show() @@ -1627,7 +1626,7 @@ class ChatActivity : } getRoomInfoTimerHandler?.postDelayed( { - chatViewModel.getRoom(conversationUser!!, roomToken) + chatViewModel.getRoom(roomToken) }, delayForRecursiveCall ) @@ -2938,8 +2937,11 @@ class ChatActivity : private fun isScrolledToBottom(): Boolean { val position = layoutManager?.findFirstVisibleItemPosition() if (position == -1) { - Log.w(TAG, "FirstVisibleItemPosition was -1 but true is returned for isScrolledToBottom(). This can " + - "happen when the UI is not yet ready") + Log.w( + TAG, + "FirstVisibleItemPosition was -1 but true is returned for isScrolledToBottom(). This can " + + "happen when the UI is not yet ready" + ) return true } @@ -3294,7 +3296,7 @@ class ChatActivity : private fun isInfoMessageAboutDeletion(currentMessage: MutableMap.MutableEntry): Boolean = currentMessage.value.parentMessageId != null && currentMessage.value.systemMessageType == ChatMessage - .SystemMessageType.MESSAGE_DELETED + .SystemMessageType.MESSAGE_DELETED private fun isReactionsMessage(currentMessage: MutableMap.MutableEntry): Boolean = currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.REACTION || @@ -3304,7 +3306,7 @@ class ChatActivity : private fun isEditMessage(currentMessage: MutableMap.MutableEntry): Boolean = currentMessage.value.parentMessageId != null && currentMessage.value.systemMessageType == ChatMessage - .SystemMessageType.MESSAGE_EDITED + .SystemMessageType.MESSAGE_EDITED private fun isPollVotedMessage(currentMessage: MutableMap.MutableEntry): Boolean = currentMessage.value.systemMessageType == ChatMessage.SystemMessageType.POLL_VOTED @@ -3404,7 +3406,7 @@ class ChatActivity : this, message, conversationUser, - currentConversation, + currentConversation ).show() } else if (hasVisibleItems(message) && !isSystemMessage(message) @@ -3614,7 +3616,7 @@ class ChatActivity : val lon = data["longitude"]!! metaData = "{\"type\":\"geo-location\",\"id\":\"geo:$lat,$lon\",\"latitude\":\"$lat\"," + - "\"longitude\":\"$lon\",\"name\":\"$name\"}" + "\"longitude\":\"$lon\",\"name\":\"$name\"}" } shareToNotes(shareUri, roomToken, message, objectId, metaData) diff --git a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt index 9c4c02e27..14607a210 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt @@ -861,7 +861,6 @@ class MessageInputFragment : Fragment() { private fun sendMessage(message: String, replyTo: Int?, sendWithoutNotification: Boolean) { chatActivity.messageInputViewModel.sendChatMessage( - conversationInternalId, chatActivity.conversationUser!!.getCredentials(), ApiUtils.getUrlForChat( chatActivity.chatApiVersion, diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt index 7f22a80a6..b0f880f01 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt @@ -14,7 +14,6 @@ import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow interface ChatMessageRepository : LifecycleAwareManager { @@ -80,6 +79,7 @@ interface ChatMessageRepository : LifecycleAwareManager { */ fun handleChatOnBackPress() + @Suppress("LongParameterList") suspend fun sendChatMessage( credentials: String, url: String, @@ -90,6 +90,7 @@ interface ChatMessageRepository : LifecycleAwareManager { referenceId: String ): Flow> + @Suppress("LongParameterList") suspend fun resendChatMessage( credentials: String, url: String, @@ -111,10 +112,7 @@ interface ChatMessageRepository : LifecycleAwareManager { suspend fun editTempChatMessage(message: ChatMessage, editedMessageText: String): Flow - suspend fun sendTempChatMessages( - credentials: String, - url: String - ) + suspend fun sendTempChatMessages(credentials: String, url: String) suspend fun deleteTempMessage(chatMessage: ChatMessage) } diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt index 70f1139e6..8f68df39f 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt @@ -115,9 +115,7 @@ data class ChatMessage( var openWhenDownloaded: Boolean = true, - var isTemporary: Boolean = false, // TODO: replace logic from message drafts with logic from temp message sending - - // var tempMessageId: Int = -1, // TODO: replace logic from message drafts with logic from temp message sending + var isTemporary: Boolean = false, var referenceId: String? = null, diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 2c456c051..8d4e25f1c 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -795,6 +795,7 @@ class OfflineFirstChatRepository @Inject constructor( scope.cancel() } + @Suppress("LongParameterList") override suspend fun sendChatMessage( credentials: String, url: String, @@ -847,6 +848,7 @@ class OfflineFirstChatRepository @Inject constructor( } } + @Suppress("LongParameterList") override suspend fun resendChatMessage( credentials: String, url: String, @@ -874,6 +876,7 @@ class OfflineFirstChatRepository @Inject constructor( ) } + @Suppress("Detekt.TooGenericExceptionCaught") override suspend fun editChatMessage( credentials: String, url: String, @@ -892,13 +895,13 @@ class OfflineFirstChatRepository @Inject constructor( } } - override suspend fun editTempChatMessage( - message: ChatMessage, editedMessageText: String - ): Flow = + @Suppress("Detekt.TooGenericExceptionCaught") + override suspend fun editTempChatMessage(message: ChatMessage, editedMessageText: String): Flow = flow { try { val messageToEdit = chatDao.getChatMessageForConversation( - internalConversationId, message.jsonMessageId + internalConversationId, + message.jsonMessageId .toLong() ).first() messageToEdit.message = editedMessageText @@ -912,10 +915,7 @@ class OfflineFirstChatRepository @Inject constructor( } } - override suspend fun sendTempChatMessages( - credentials: String, - url: String - ) { + override suspend fun sendTempChatMessages(credentials: String, url: String) { val tempMessages = chatDao.getTempMessagesForConversation(internalConversationId).first() tempMessages.sortedBy { it.internalId }.onEach { sendChatMessage( @@ -974,7 +974,6 @@ class OfflineFirstChatRepository @Inject constructor( message: String, referenceId: String ): ChatMessageEntity { - val currentTimeMillies = System.currentTimeMillis() val currentTimeWithoutYear = SendMessageUtils().removeYearFromTimestamp(currentTimeMillies) diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt index 27e96517e..6f857d254 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt @@ -117,7 +117,7 @@ class RetrofitChatNetwork( displayName, null, false, - SendMessageUtils().generateReferenceId() // TODO add temp message before with ref id.. + SendMessageUtils().generateReferenceId() ).map { it } diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt index 6a84780f9..9366a6302 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt @@ -247,14 +247,9 @@ class ChatViewModel @Inject constructor( chatRepository.setData(conversationModel, credentials, urlForChatting) } - fun getRoom(user: User, token: String) { + fun getRoom(token: String) { _getRoomViewState.value = GetRoomStartState conversationRepository.getRoom(token) - - // chatNetworkDataSource.getRoom(user, token) - // .subscribeOn(Schedulers.io()) - // ?.observeOn(AndroidSchedulers.mainThread()) - // ?.subscribe(GetRoomObserver()) } fun getCapabilities(user: User, token: String, conversationModel: ConversationModel) { @@ -677,25 +672,6 @@ class ChatViewModel @Inject constructor( fun getPlaybackSpeedPreference(message: ChatMessage) = _voiceMessagePlaybackSpeedPreferences.value?.get(message.user.id) ?: PlaybackSpeed.NORMAL -// inner class GetRoomObserver : Observer { -// override fun onSubscribe(d: Disposable) { -// // unused atm -// } -// -// override fun onNext(conversationModel: ConversationModel) { -// _getRoomViewState.value = GetRoomSuccessState(conversationModel) -// } -// -// override fun onError(e: Throwable) { -// Log.e(TAG, "Error when fetching room") -// _getRoomViewState.value = GetRoomErrorState -// } -// -// override fun onComplete() { -// // unused atm -// } -// } - inner class JoinRoomObserver : Observer { override fun onSubscribe(d: Disposable) { disposableSet.add(d) @@ -800,10 +776,7 @@ class ChatViewModel @Inject constructor( } } - fun resendMessage( - credentials: String, - urlForChat: String, - message: ChatMessage) { + fun resendMessage(credentials: String, urlForChat: String, message: ChatMessage) { viewModelScope.launch { chatRepository.resendChatMessage( credentials, diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index 49216d7d7..e8c6cbfaa 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -14,23 +14,18 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.nextcloud.talk.chat.data.ChatMessageRepository import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager import com.nextcloud.talk.chat.data.io.AudioRecorderManager import com.nextcloud.talk.chat.data.io.MediaPlayerManager import com.nextcloud.talk.chat.data.model.ChatMessage -import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.utils.message.SendMessageUtils import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.commons.models.IMessage import io.reactivex.disposables.Disposable -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import java.lang.Thread.sleep import javax.inject.Inject class MessageInputViewModel @Inject constructor( @@ -51,7 +46,7 @@ class MessageInputViewModel @Inject constructor( lateinit var currentLifeCycleFlag: LifeCycleFlag val disposableSet = mutableSetOf() - fun setData(chatMessageRepository: ChatMessageRepository){ + fun setData(chatMessageRepository: ChatMessageRepository) { chatRepository = chatMessageRepository } @@ -103,9 +98,11 @@ class MessageInputViewModel @Inject constructor( object SendChatMessageStartState : ViewState class SendChatMessageSuccessState(val message: CharSequence) : ViewState class SendChatMessageErrorState(val message: CharSequence) : ViewState + private val _sendChatMessageViewState: MutableLiveData = MutableLiveData(SendChatMessageStartState) val sendChatMessageViewState: LiveData get() = _sendChatMessageViewState + object EditMessageErrorState : ViewState class EditMessageSuccessState(val messageEdited: ChatOverallSingleMessage) : ViewState @@ -121,9 +118,7 @@ class MessageInputViewModel @Inject constructor( val callStartedFlow: LiveData> get() = _callStartedFlow - @Suppress("LongParameterList") fun sendChatMessage( - internalId: String, credentials: String, url: String, message: String, @@ -172,10 +167,7 @@ class MessageInputViewModel @Inject constructor( } } - fun sendTempMessages( - credentials: String, - url: String, - ) { + fun sendTempMessages(credentials: String, url: String) { viewModelScope.launch { chatRepository.sendTempChatMessages( credentials, diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt index afa64efa5..b81a1604b 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt @@ -65,7 +65,6 @@ import com.nextcloud.talk.utils.preferences.AppPreferences import dagger.Module import dagger.Provides import okhttp3.OkHttpClient -import javax.inject.Singleton @Module class RepositoryModule { diff --git a/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt index 5349794c3..48bbc4bc8 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt @@ -66,6 +66,6 @@ data class ChatMessageEntity( @ColumnInfo(name = "referenceId") var referenceId: String? = null, @ColumnInfo(name = "sendingFailed") var sendingFailed: Boolean = false, @ColumnInfo(name = "systemMessage") var systemMessageType: ChatMessage.SystemMessageType, - @ColumnInfo(name = "timestamp") var timestamp: Long = 0, + @ColumnInfo(name = "timestamp") var timestamp: Long = 0 // missing/not needed: silent ) diff --git a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt index c70fe8b2f..223fed968 100644 --- a/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt +++ b/app/src/main/java/com/nextcloud/talk/receivers/DirectReplyReceiver.kt @@ -87,7 +87,7 @@ class DirectReplyReceiver : BroadcastReceiver() { currentUser.displayName, null, false, - SendMessageUtils().generateReferenceId() // TODO add temp chatMessage before with ref id... + SendMessageUtils().generateReferenceId() ) ?.subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt index a3403db46..4d2900234 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt @@ -58,19 +58,15 @@ class TempMessageActionsDialog( viewThemeUtils.material.colorBottomSheetBackground(binding.root) viewThemeUtils.material.colorBottomSheetDragHandle(binding.bottomSheetDragHandle) - - initMenuItemCopy(!message.isDeleted) - val apiVersion = ApiUtils.getConversationApiVersion(user!!, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1)) - - initMenuItems() } private fun initMenuItems() { this.lifecycleScope.launch { initResendMessage(networkMonitor.isOnline.first()) - initMenuEditMessage(true) - initMenuDeleteMessage(true) + initMenuEditMessage() + initMenuDeleteMessage() + initMenuItemCopy() } } @@ -99,34 +95,25 @@ class TempMessageActionsDialog( binding.menuResendMessage.visibility = getVisibility(visible) } - private fun initMenuDeleteMessage(visible: Boolean) { - if (visible) { - binding.menuDeleteMessage.setOnClickListener { - chatActivity.chatViewModel.deleteTempMessage(message) - dismiss() - } + private fun initMenuDeleteMessage() { + binding.menuDeleteMessage.setOnClickListener { + chatActivity.chatViewModel.deleteTempMessage(message) + dismiss() } - binding.menuDeleteMessage.visibility = getVisibility(visible) } - private fun initMenuEditMessage(visible: Boolean) { + private fun initMenuEditMessage() { binding.menuEditMessage.setOnClickListener { chatActivity.messageInputViewModel.edit(message) dismiss() } - - binding.menuEditMessage.visibility = getVisibility(visible) } - private fun initMenuItemCopy(visible: Boolean) { - if (visible) { - binding.menuCopyMessage.setOnClickListener { - chatActivity.copyMessage(message) - dismiss() - } + private fun initMenuItemCopy() { + binding.menuCopyMessage.setOnClickListener { + chatActivity.copyMessage(message) + dismiss() } - - binding.menuCopyMessage.visibility = getVisibility(visible) } private fun getVisibility(visible: Boolean): Int { diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt index 39bfa5460..e1809590f 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt @@ -17,7 +17,6 @@ import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore import com.nextcloud.talk.R -import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel import com.nextcloud.talk.ui.PlaybackSpeed import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async From 3dca00bac00b15937c82b974e7543e862ceadc66 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 3 Jan 2025 18:26:58 +0100 Subject: [PATCH 28/40] resolve warnings Signed-off-by: Marcel Hibbe --- .../com/nextcloud/talk/chat/ChatActivity.kt | 5 +- .../network/OfflineFirstChatRepository.kt | 5 +- .../talk/chat/viewmodels/ChatViewModel.kt | 2 +- .../chat/viewmodels/MessageInputViewModel.kt | 13 +-- .../ui/dialog/TempMessageActionsDialog.kt | 6 +- .../talk/utils/message/SendMessageUtils.kt | 4 - .../utils/preferences/AppPreferencesImpl.kt | 7 -- .../main/res/drawable/baseline_replay_24.xml | 5 -- .../drawable/baseline_report_problem_24.xml | 19 +++- .../res/layout/item_temporary_message.xml | 90 ------------------- 10 files changed, 22 insertions(+), 134 deletions(-) delete mode 100644 app/src/main/res/drawable/baseline_replay_24.xml delete mode 100644 app/src/main/res/layout/item_temporary_message.xml diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index b46cc5227..1f04945bd 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -3404,9 +3404,7 @@ class ChatActivity : if (message.isTemporary) { TempMessageActionsDialog( this, - message, - conversationUser, - currentConversation + message ).show() } else if (hasVisibleItems(message) && !isSystemMessage(message) @@ -4028,7 +4026,6 @@ class ChatActivity : private const val CONTENT_TYPE_LINK_PREVIEW: Byte = 7 private const val CONTENT_TYPE_DECK_CARD: Byte = 8 private const val UNREAD_MESSAGES_MARKER_ID = -1 - private const val CALL_STARTED_ID = -2 private const val GET_ROOM_INFO_DELAY_NORMAL: Long = 30000 private const val GET_ROOM_INFO_DELAY_LOBBY: Long = 5000 private const val AGE_THRESHOLD_FOR_DELETE_MESSAGE: Int = 21600000 // (6 hours in millis = 6 * 3600 * 1000) diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 8d4e25f1c..9608dfd9f 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -29,7 +29,6 @@ import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew import com.nextcloud.talk.utils.message.SendMessageUtils -import com.nextcloud.talk.utils.preferences.AppPreferences import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope @@ -52,9 +51,8 @@ class OfflineFirstChatRepository @Inject constructor( private val chatDao: ChatMessagesDao, private val chatBlocksDao: ChatBlocksDao, private val network: ChatNetworkDataSource, - private val datastore: AppPreferences, private val monitor: NetworkMonitor, - private val userProvider: CurrentUserProviderNew + userProvider: CurrentUserProviderNew ) : ChatMessageRepository { val currentUser: User = userProvider.currentUser.blockingGet() @@ -941,6 +939,7 @@ class OfflineFirstChatRepository @Inject constructor( _removeMessageFlow.emit(chatMessage) } + @Suppress("Detekt.TooGenericExceptionCaught") override suspend fun addTemporaryMessage( message: CharSequence, displayName: String, diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt index 9366a6302..bb2abef36 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/ChatViewModel.kt @@ -651,7 +651,7 @@ class ChatViewModel @Inject constructor( chatRepository.handleChatOnBackPress() } - suspend fun getMessageById(url: String, conversationModel: ConversationModel, messageId: Long): Flow = + fun getMessageById(url: String, conversationModel: ConversationModel, messageId: Long): Flow = flow { val bundle = Bundle() bundle.putString(BundleKeys.KEY_CHAT_URL, url) diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index e8c6cbfaa..c8e313c45 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -22,7 +22,6 @@ import com.nextcloud.talk.chat.data.io.MediaPlayerManager import com.nextcloud.talk.chat.data.model.ChatMessage import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage import com.nextcloud.talk.utils.message.SendMessageUtils -import com.nextcloud.talk.utils.preferences.AppPreferences import com.stfalcon.chatkit.commons.models.IMessage import io.reactivex.disposables.Disposable import kotlinx.coroutines.launch @@ -31,8 +30,7 @@ import javax.inject.Inject class MessageInputViewModel @Inject constructor( private val audioRecorderManager: AudioRecorderManager, private val mediaPlayerManager: MediaPlayerManager, - private val audioFocusRequestManager: AudioFocusRequestManager, - private val appPreferences: AppPreferences + private val audioFocusRequestManager: AudioFocusRequestManager ) : ViewModel(), DefaultLifecycleObserver { @@ -197,13 +195,7 @@ class MessageInputViewModel @Inject constructor( chatRepository.editTempChatMessage( message, editedMessageText - ).collect { result -> - if (true) { - // _editMessageViewState.value = EditMessageSuccessState(result) - } else { - // _editMessageViewState.value = EditMessageErrorState - } - } + ).collect {} } } @@ -262,6 +254,5 @@ class MessageInputViewModel @Inject constructor( companion object { private val TAG = MessageInputViewModel::class.java.simpleName - private const val DELAY_BETWEEN_QUEUED_MESSAGES: Long = 1000 } } diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt index 4d2900234..366bf5355 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt @@ -19,9 +19,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.chat.ChatActivity import com.nextcloud.talk.chat.data.model.ChatMessage import com.nextcloud.talk.data.network.NetworkMonitor -import com.nextcloud.talk.data.user.model.User import com.nextcloud.talk.databinding.DialogTempMessageActionsBinding -import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.ui.theme.ViewThemeUtils import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.DateUtils @@ -32,9 +30,7 @@ import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) class TempMessageActionsDialog( private val chatActivity: ChatActivity, - private val message: ChatMessage, - private val user: User?, - private val currentConversation: ConversationModel? + private val message: ChatMessage ) : BottomSheetDialog(chatActivity) { @Inject diff --git a/app/src/main/java/com/nextcloud/talk/utils/message/SendMessageUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/message/SendMessageUtils.kt index 4d42bfa6a..547cc6c58 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/message/SendMessageUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/message/SendMessageUtils.kt @@ -29,8 +29,4 @@ class SendMessageUtils { val second = calendar.get(Calendar.SECOND) return (month * 1000000) + (day * 10000) + (hour * 100) + (minute * 10) + second } - - companion object { - private val TAG = SendMessageUtils::class.java.simpleName - } } diff --git a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt index e1809590f..d0867b6f8 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/preferences/AppPreferencesImpl.kt @@ -584,13 +584,7 @@ class AppPreferencesImpl(val context: Context) : AppPreferences { @Suppress("UnusedPrivateProperty") private val TAG = AppPreferencesImpl::class.simpleName private val Context.dataStore: DataStore by preferencesDataStore(name = "settings") - private const val ID: Int = 0 - private const val MESSAGE_INDEX: Int = 1 - private const val REPLY_TO_INDEX: Int = 2 - private const val DISPLAY_NAME_INDEX: Int = 3 - private const val SILENT_INDEX: Int = 4 const val PROXY_TYPE = "proxy_type" - const val PROXY_SERVER = "proxy_server" const val PROXY_HOST = "proxy_host" const val PROXY_PORT = "proxy_port" const val PROXY_CRED = "proxy_credentials" @@ -615,7 +609,6 @@ class AppPreferencesImpl(val context: Context) : AppPreferences { const val DB_ROOM_MIGRATED = "db_room_migrated" const val PHONE_BOOK_INTEGRATION_LAST_RUN = "phone_book_integration_last_run" const val TYPING_STATUS = "typing_status" - const val MESSAGE_QUEUE = "@message_queue" const val VOICE_MESSAGE_PLAYBACK_SPEEDS = "voice_message_playback_speeds" const val SHOW_REGULAR_NOTIFICATION_WARNING = "show_regular_notification_warning" const val LAST_NOTIFICATION_WARNING = "last_notification_warning" diff --git a/app/src/main/res/drawable/baseline_replay_24.xml b/app/src/main/res/drawable/baseline_replay_24.xml deleted file mode 100644 index 58390a9c3..000000000 --- a/app/src/main/res/drawable/baseline_replay_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/baseline_report_problem_24.xml b/app/src/main/res/drawable/baseline_report_problem_24.xml index a17ce9ad9..697250acc 100644 --- a/app/src/main/res/drawable/baseline_report_problem_24.xml +++ b/app/src/main/res/drawable/baseline_report_problem_24.xml @@ -1,5 +1,16 @@ - - - - + + + diff --git a/app/src/main/res/layout/item_temporary_message.xml b/app/src/main/res/layout/item_temporary_message.xml deleted file mode 100644 index 9f3ea981e..000000000 --- a/app/src/main/res/layout/item_temporary_message.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - From 560f955002fa423ca12dd3ab7e8aff4312cf89df Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 6 Jan 2025 12:03:26 +0100 Subject: [PATCH 29/40] comment in opHelperFactory Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/data/source/local/TalkDatabase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt index 8fa20e9d3..5c3656c76 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/TalkDatabase.kt @@ -108,7 +108,7 @@ abstract class TalkDatabase : RoomDatabase() { return Room .databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName) // comment out openHelperFactory to view the database entries in Android Studio for debugging - // .openHelperFactory(factory) + .openHelperFactory(factory) .addMigrations( Migrations.MIGRATION_6_8, Migrations.MIGRATION_7_8, From a58607b54705364fba42b012be9206d49ce6144a Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 6 Jan 2025 12:41:03 +0100 Subject: [PATCH 30/40] resolve detekt warnings Signed-off-by: Marcel Hibbe --- .../OutcomingTextMessageViewHolder.kt | 1 + .../com/nextcloud/talk/api/NcApiCoroutines.kt | 1 + .../network/OfflineFirstChatRepository.kt | 55 ++++++++++--------- .../chat/viewmodels/MessageInputViewModel.kt | 2 + .../talk/dagger/modules/RepositoryModule.kt | 3 - .../talk/data/database/dao/ChatMessagesDao.kt | 1 + 6 files changed, 35 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 086038720..be4c238d9 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -64,6 +64,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : lateinit var commonMessageInterface: CommonMessageInterface + @Suppress("Detekt.LongMethod") override fun onBind(message: ChatMessage) { super.onBind(message) sharedApplication!!.componentApplication.inject(this) diff --git a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt index 88ebffd82..af291d4cf 100644 --- a/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt +++ b/app/src/main/java/com/nextcloud/talk/api/NcApiCoroutines.kt @@ -31,6 +31,7 @@ import retrofit2.http.Query import retrofit2.http.QueryMap import retrofit2.http.Url +@Suppress("TooManyFunctions") interface NcApiCoroutines { @GET @JvmSuppressWildcards diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 9608dfd9f..854c01ce6 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -47,6 +47,7 @@ import kotlinx.coroutines.launch import java.io.IOException import javax.inject.Inject +@Suppress("LargeClass", "TooManyFunctions") class OfflineFirstChatRepository @Inject constructor( private val chatDao: ChatMessagesDao, private val chatBlocksDao: ChatBlocksDao, @@ -178,35 +179,39 @@ class OfflineFirstChatRepository @Inject constructor( Log.d(TAG, "newestMessageIdFromDb after sync: $newestMessageIdFromDb") } - if (newestMessageIdFromDb.toInt() != 0) { - val limit = getCappedMessagesAmountOfChatBlock(newestMessageIdFromDb) - - val list = getMessagesBeforeAndEqual( - newestMessageIdFromDb, - internalConversationId, - limit - ) - if (list.isNotEmpty()) { - handleNewAndTempMessages( - receivedChatMessages = list, - lookIntoFuture = false, - showUnreadMessagesMarker = false - ) - } - - sendTempChatMessages(credentials, urlForChatting) - - // delay is a dirty workaround to make sure messages are added to adapter on initial load before dealing - // with them (otherwise there is a race condition). - delay(DELAY_TO_ENSURE_MESSAGES_ARE_ADDED) - - updateUiForLastCommonRead() - updateUiForLastReadMessage(newestMessageIdFromDb) - } + handleMessagesFromDb(newestMessageIdFromDb) initMessagePolling(newestMessageIdFromDb) } + private suspend fun handleMessagesFromDb(newestMessageIdFromDb: Long) { + if (newestMessageIdFromDb.toInt() != 0) { + val limit = getCappedMessagesAmountOfChatBlock(newestMessageIdFromDb) + + val list = getMessagesBeforeAndEqual( + newestMessageIdFromDb, + internalConversationId, + limit + ) + if (list.isNotEmpty()) { + handleNewAndTempMessages( + receivedChatMessages = list, + lookIntoFuture = false, + showUnreadMessagesMarker = false + ) + } + + sendTempChatMessages(credentials, urlForChatting) + + // delay is a dirty workaround to make sure messages are added to adapter on initial load before dealing + // with them (otherwise there is a race condition). + delay(DELAY_TO_ENSURE_MESSAGES_ARE_ADDED) + + updateUiForLastCommonRead() + updateUiForLastReadMessage(newestMessageIdFromDb) + } + } + private suspend fun getCappedMessagesAmountOfChatBlock(messageId: Long): Int { val chatBlock = getBlockOfMessage(messageId.toInt()) diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index c8e313c45..cc144f8c1 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -27,6 +27,7 @@ import io.reactivex.disposables.Disposable import kotlinx.coroutines.launch import javax.inject.Inject +@Suppress("Detekt.TooManyFunctions") class MessageInputViewModel @Inject constructor( private val audioRecorderManager: AudioRecorderManager, private val mediaPlayerManager: MediaPlayerManager, @@ -116,6 +117,7 @@ class MessageInputViewModel @Inject constructor( val callStartedFlow: LiveData> get() = _callStartedFlow + @Suppress("LongParameterList") fun sendChatMessage( credentials: String, url: String, diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt index b81a1604b..b79b4d573 100644 --- a/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt +++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/RepositoryModule.kt @@ -61,7 +61,6 @@ import com.nextcloud.talk.translate.repositories.TranslateRepositoryImpl import com.nextcloud.talk.users.UserManager import com.nextcloud.talk.utils.DateUtils import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew -import com.nextcloud.talk.utils.preferences.AppPreferences import dagger.Module import dagger.Provides import okhttp3.OkHttpClient @@ -156,7 +155,6 @@ class RepositoryModule { chatMessagesDao: ChatMessagesDao, chatBlocksDao: ChatBlocksDao, dataSource: ChatNetworkDataSource, - appPreferences: AppPreferences, networkMonitor: NetworkMonitor, userProvider: CurrentUserProviderNew ): ChatMessageRepository = @@ -164,7 +162,6 @@ class RepositoryModule { chatMessagesDao, chatBlocksDao, dataSource, - appPreferences, networkMonitor, userProvider ) diff --git a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt index 7fb423767..1008ce853 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt @@ -16,6 +16,7 @@ import com.nextcloud.talk.data.database.model.ChatMessageEntity import kotlinx.coroutines.flow.Flow @Dao +@Suppress("Detekt.TooManyFunctions") interface ChatMessagesDao { @Query( """ From 4c795139ac3b2b8b25faf56c68b5fcb5dbca08c5 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 6 Jan 2025 13:04:21 +0100 Subject: [PATCH 31/40] add DB migration Signed-off-by: Marcel Hibbe --- .../talk/data/source/local/Migrations.kt | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt index 8d10b94b3..aacd83375 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt @@ -51,7 +51,7 @@ object Migrations { val MIGRATION_12_13 = object : Migration(12, 13) { override fun migrate(db: SupportSQLiteDatabase) { Log.i("Migrations", "Migrating 12 to 13") - addReferenceIdToChatMessages(db) + addTempMessagesSupport(db) } } @@ -265,7 +265,7 @@ object Migrations { } } - fun addReferenceIdToChatMessages(db: SupportSQLiteDatabase) { + fun addTempMessagesSupport(db: SupportSQLiteDatabase) { try { db.execSQL( "ALTER TABLE ChatMessages " + @@ -274,5 +274,23 @@ object Migrations { } catch (e: SQLException) { Log.i("Migrations", "Something went wrong when adding column referenceId to table ChatMessages") } + + try { + db.execSQL( + "ALTER TABLE ChatMessages " + + "ADD COLUMN isTemporary INTEGER NOT NULL DEFAULT 0;" + ) + } catch (e: SQLException) { + Log.i("Migrations", "Something went wrong when adding column isTemporary to table ChatMessages") + } + + try { + db.execSQL( + "ALTER TABLE ChatMessages " + + "ADD COLUMN sendingFailed INTEGER NOT NULL DEFAULT 0;" + ) + } catch (e: SQLException) { + Log.i("Migrations", "Something went wrong when adding column sendingFailed to table ChatMessages") + } } } From 3fdaa4bdcd05d214f01a8503f2f1117de2179fc6 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 6 Jan 2025 14:39:12 +0100 Subject: [PATCH 32/40] fix parent message for temp messages Signed-off-by: Marcel Hibbe --- .../data/network/OfflineFirstChatRepository.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 854c01ce6..47d00c55b 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -26,6 +26,8 @@ import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.models.json.chat.ChatMessageJson import com.nextcloud.talk.models.json.chat.ChatOverall import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage +import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter +import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew import com.nextcloud.talk.utils.message.SendMessageUtils @@ -956,6 +958,7 @@ class OfflineFirstChatRepository @Inject constructor( val tempChatMessageEntity = createChatMessageEntity( internalConversationId, message.toString(), + replyTo, referenceId ) @@ -976,25 +979,32 @@ class OfflineFirstChatRepository @Inject constructor( private fun createChatMessageEntity( internalConversationId: String, message: String, + replyTo: Int, referenceId: String ): ChatMessageEntity { val currentTimeMillies = System.currentTimeMillis() val currentTimeWithoutYear = SendMessageUtils().removeYearFromTimestamp(currentTimeMillies) + val parentMessageId = if (replyTo != 0) { + replyTo.toLong() + } else { + null + } + val entity = ChatMessageEntity( - internalId = internalConversationId + "@_temp_" + currentTimeMillies, + internalId = "$internalConversationId@_temp_$currentTimeMillies", internalConversationId = internalConversationId, id = currentTimeWithoutYear.toLong(), message = message, deleted = false, token = conversationModel.token, actorId = currentUser.userId!!, - actorType = "users", + actorType = EnumActorTypeConverter().convertToString(Participant.ActorType.USERS), accountId = currentUser.id!!, messageParameters = null, messageType = "comment", - parentMessageId = null, + parentMessageId = parentMessageId, systemMessageType = ChatMessage.SystemMessageType.DUMMY, replyable = false, timestamp = System.currentTimeMillis() / MILLIES, From f665b1c116cf4146e02d0aceaf3726c8e1872e39 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 6 Jan 2025 15:12:45 +0100 Subject: [PATCH 33/40] save "silent" in chat messages (incl DB) Signed-off-by: Marcel Hibbe --- .../13.json | 36 +++++++++++-------- .../talk/chat/data/ChatMessageRepository.kt | 1 + .../talk/chat/data/model/ChatMessage.kt | 4 ++- .../network/OfflineFirstChatRepository.kt | 10 ++++-- .../chat/viewmodels/MessageInputViewModel.kt | 1 + .../database/mappers/ChatMessageMapUtils.kt | 9 +++-- .../data/database/model/ChatMessageEntity.kt | 2 +- .../talk/data/source/local/Migrations.kt | 9 +++++ .../talk/models/json/chat/ChatMessageJson.kt | 3 +- 9 files changed, 51 insertions(+), 24 deletions(-) diff --git a/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json index 3b4330bb9..2e2345001 100644 --- a/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json +++ b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/13.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 13, - "identityHash": "ec1e16b220080592a488165e493b4f89", + "identityHash": "a521f027909f69f4c7d1855f84a2e67f", "entities": [ { "tableName": "User", @@ -450,7 +450,7 @@ }, { "tableName": "ChatMessages", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `systemMessage` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `isTemporary` INTEGER NOT NULL, `sendingFailed` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT NOT NULL, `id` INTEGER NOT NULL, `internalConversationId` TEXT NOT NULL, `actorDisplayName` TEXT NOT NULL, `message` TEXT NOT NULL, `actorId` TEXT NOT NULL, `actorType` TEXT NOT NULL, `deleted` INTEGER NOT NULL, `expirationTimestamp` INTEGER NOT NULL, `isReplyable` INTEGER NOT NULL, `isTemporary` INTEGER NOT NULL, `lastEditActorDisplayName` TEXT, `lastEditActorId` TEXT, `lastEditActorType` TEXT, `lastEditTimestamp` INTEGER, `markdown` INTEGER, `messageParameters` TEXT, `messageType` TEXT NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `referenceId` TEXT, `sendingFailed` INTEGER NOT NULL, `silent` INTEGER NOT NULL, `systemMessage` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, PRIMARY KEY(`internalId`), FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) ON UPDATE CASCADE ON DELETE CASCADE )", "fields": [ { "fieldPath": "internalId", @@ -524,6 +524,12 @@ "affinity": "INTEGER", "notNull": true }, + { + "fieldPath": "isTemporary", + "columnName": "isTemporary", + "affinity": "INTEGER", + "notNull": true + }, { "fieldPath": "lastEditActorDisplayName", "columnName": "lastEditActorDisplayName", @@ -590,6 +596,18 @@ "affinity": "TEXT", "notNull": false }, + { + "fieldPath": "sendingFailed", + "columnName": "sendingFailed", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "silent", + "columnName": "silent", + "affinity": "INTEGER", + "notNull": true + }, { "fieldPath": "systemMessageType", "columnName": "systemMessage", @@ -601,18 +619,6 @@ "columnName": "timestamp", "affinity": "INTEGER", "notNull": true - }, - { - "fieldPath": "isTemporary", - "columnName": "isTemporary", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "sendingFailed", - "columnName": "sendingFailed", - "affinity": "INTEGER", - "notNull": true } ], "primaryKey": { @@ -737,7 +743,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ec1e16b220080592a488165e493b4f89')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a521f027909f69f4c7d1855f84a2e67f')" ] } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt index b0f880f01..d42c3bff3 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt @@ -105,6 +105,7 @@ interface ChatMessageRepository : LifecycleAwareManager { message: CharSequence, displayName: String, replyTo: Int, + sendWithoutNotification: Boolean, referenceId: String ): Flow> diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt index 8f68df39f..f80886fa4 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt @@ -119,7 +119,9 @@ data class ChatMessage( var referenceId: String? = null, - var sendingFailed: Boolean = true + var sendingFailed: Boolean = true, + + var silent: Boolean = false ) : MessageContentType, MessageContentType.Image { diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt index 47d00c55b..08cf1b832 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt @@ -929,7 +929,7 @@ class OfflineFirstChatRepository @Inject constructor( it.message, it.actorDisplayName, it.parentMessageId?.toIntOrZero() ?: 0, - false, + it.silent, it.referenceId.orEmpty() ).collect { result -> if (result.isSuccess) { @@ -951,6 +951,7 @@ class OfflineFirstChatRepository @Inject constructor( message: CharSequence, displayName: String, replyTo: Int, + sendWithoutNotification: Boolean, referenceId: String ): Flow> = flow { @@ -959,6 +960,7 @@ class OfflineFirstChatRepository @Inject constructor( internalConversationId, message.toString(), replyTo, + sendWithoutNotification, referenceId ) @@ -980,6 +982,7 @@ class OfflineFirstChatRepository @Inject constructor( internalConversationId: String, message: String, replyTo: Int, + sendWithoutNotification: Boolean, referenceId: String ): ChatMessageEntity { val currentTimeMillies = System.currentTimeMillis() @@ -1007,12 +1010,13 @@ class OfflineFirstChatRepository @Inject constructor( parentMessageId = parentMessageId, systemMessageType = ChatMessage.SystemMessageType.DUMMY, replyable = false, - timestamp = System.currentTimeMillis() / MILLIES, + timestamp = currentTimeMillies / MILLIES, expirationTimestamp = 0, actorDisplayName = currentUser.displayName!!, referenceId = referenceId, isTemporary = true, - sendingFailed = false + sendingFailed = false, + silent = sendWithoutNotification ) return entity } diff --git a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt index cc144f8c1..02299d309 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/viewmodels/MessageInputViewModel.kt @@ -134,6 +134,7 @@ class MessageInputViewModel @Inject constructor( message, displayName, replyTo, + sendWithoutNotification, referenceId ).collect { result -> if (result.isSuccess) { diff --git a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt index 8e3985ad0..7aa236369 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt @@ -39,7 +39,8 @@ fun ChatMessageJson.asEntity(accountId: Long) = lastEditActorType = lastEditActorType, lastEditTimestamp = lastEditTimestamp, deleted = deleted, - referenceId = referenceId + referenceId = referenceId, + silent = silent ) fun ChatMessageEntity.asModel() = @@ -68,7 +69,8 @@ fun ChatMessageEntity.asModel() = referenceId = referenceId, isTemporary = isTemporary, sendingFailed = sendingFailed, - readStatus = ReadStatus.NONE + readStatus = ReadStatus.NONE, + silent = silent ) fun ChatMessageJson.asModel() = @@ -94,5 +96,6 @@ fun ChatMessageJson.asModel() = lastEditActorType = lastEditActorType, lastEditTimestamp = lastEditTimestamp, isDeleted = deleted, - referenceId = referenceId + referenceId = referenceId, + silent = silent ) diff --git a/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt index 48bbc4bc8..c00b2c2e7 100644 --- a/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt @@ -65,7 +65,7 @@ data class ChatMessageEntity( @ColumnInfo(name = "reactionsSelf") var reactionsSelf: ArrayList? = null, @ColumnInfo(name = "referenceId") var referenceId: String? = null, @ColumnInfo(name = "sendingFailed") var sendingFailed: Boolean = false, + @ColumnInfo(name = "silent") var silent: Boolean = false, @ColumnInfo(name = "systemMessage") var systemMessageType: ChatMessage.SystemMessageType, @ColumnInfo(name = "timestamp") var timestamp: Long = 0 - // missing/not needed: silent ) diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt index aacd83375..85c3239be 100644 --- a/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt +++ b/app/src/main/java/com/nextcloud/talk/data/source/local/Migrations.kt @@ -292,5 +292,14 @@ object Migrations { } catch (e: SQLException) { Log.i("Migrations", "Something went wrong when adding column sendingFailed to table ChatMessages") } + + try { + db.execSQL( + "ALTER TABLE ChatMessages " + + "ADD COLUMN silent INTEGER NOT NULL DEFAULT 0;" + ) + } catch (e: SQLException) { + Log.i("Migrations", "Something went wrong when adding column silent to table ChatMessages") + } } } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt index 60d039704..47d056b41 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt @@ -43,5 +43,6 @@ data class ChatMessageJson( @JsonField(name = ["lastEditActorType"]) var lastEditActorType: String? = null, @JsonField(name = ["lastEditTimestamp"]) var lastEditTimestamp: Long? = 0, @JsonField(name = ["deleted"]) var deleted: Boolean = false, - @JsonField(name = ["referenceId"]) var referenceId: String? = null + @JsonField(name = ["referenceId"]) var referenceId: String? = null, + @JsonField(name = ["silent"]) var silent: Boolean = false, ) : Parcelable From 1731ca0985d71e766756b91c3077054e57c54493 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 6 Jan 2025 15:42:56 +0100 Subject: [PATCH 34/40] remove flickering of status icons by moving networkMonitor.isOnline to separate check and by setting binding.checkMark.visibility = View.INVISIBLE binding.sendingProgress.visibility = View.GONE before setting the status icons to to handle recyclerview behavior Signed-off-by: Marcel Hibbe --- .../OutcomingTextMessageViewHolder.kt | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index be4c238d9..8da9c07b2 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -120,26 +120,31 @@ class OutcomingTextMessageViewHolder(itemView: View) : binding.messageQuote.quotedChatMessageView.visibility = View.GONE } + binding.checkMark.visibility = View.INVISIBLE + binding.sendingProgress.visibility = View.GONE + + if (message.sendingFailed) { + updateStatus( + R.drawable.baseline_report_problem_24, + "failed" + ) + binding.bubble.setOnClickListener { + commonMessageInterface.onOpenMessageActionsDialog(message) + } + } else if (message.isTemporary) { + showSendingSpinner() + } else if (message.readStatus == ReadStatus.READ) { + updateStatus(R.drawable.ic_check_all, context.resources?.getString(R.string.nc_message_read)) + } else if (message.readStatus == ReadStatus.SENT) { + updateStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent)) + } + CoroutineScope(Dispatchers.Main).launch { if (message.isTemporary && !networkMonitor.isOnline.first()) { updateStatus( R.drawable.ic_signal_wifi_off_white_24dp, "offline" ) - } else if (message.sendingFailed) { - updateStatus( - R.drawable.baseline_report_problem_24, - "failed" - ) - binding.bubble.setOnClickListener { - commonMessageInterface.onOpenMessageActionsDialog(message) - } - } else if (message.isTemporary) { - showSendingSpinner() - } else if (message.readStatus == ReadStatus.READ) { - updateStatus(R.drawable.ic_check_all, context.resources?.getString(R.string.nc_message_read)) - } else if (message.readStatus == ReadStatus.SENT) { - updateStatus(R.drawable.ic_check, context.resources?.getString(R.string.nc_message_sent)) } } From 1504e514999ee7d347e2a9220a0a51999398f8a4 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 6 Jan 2025 15:46:05 +0100 Subject: [PATCH 35/40] add strings for message status icons Signed-off-by: Marcel Hibbe --- .../talk/adapters/messages/OutcomingTextMessageViewHolder.kt | 4 ++-- app/src/main/res/values/strings.xml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 8da9c07b2..652d3fc26 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -126,7 +126,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : if (message.sendingFailed) { updateStatus( R.drawable.baseline_report_problem_24, - "failed" + context.resources?.getString(R.string.nc_message_failed) ) binding.bubble.setOnClickListener { commonMessageInterface.onOpenMessageActionsDialog(message) @@ -143,7 +143,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : if (message.isTemporary && !networkMonitor.isOnline.first()) { updateStatus( R.drawable.ic_signal_wifi_off_white_24dp, - "offline" + context.resources?.getString(R.string.nc_message_offline) ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 89ea1fa50..0d20e2544 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -433,6 +433,8 @@ How to translate with transifex: You: %1$s Message read Message sent + Offline + Failed Failed to send message: Remote audio off Add attachment From 3094054b73d4ebcab844790db94f49a6d3d78341 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 6 Jan 2025 16:03:38 +0100 Subject: [PATCH 36/40] change visibility of temp message actions during sending: edit and delete should not be shown.. Signed-off-by: Marcel Hibbe --- .../ui/dialog/TempMessageActionsDialog.kt | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt index 366bf5355..a75592532 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/TempMessageActionsDialog.kt @@ -59,9 +59,10 @@ class TempMessageActionsDialog( private fun initMenuItems() { this.lifecycleScope.launch { - initResendMessage(networkMonitor.isOnline.first()) - initMenuEditMessage() - initMenuDeleteMessage() + val isOnline = networkMonitor.isOnline.first() + initResendMessage(message.sendingFailed && isOnline) + initMenuEditMessage(message.sendingFailed || !isOnline) + initMenuDeleteMessage(message.sendingFailed || !isOnline) initMenuItemCopy() } } @@ -91,18 +92,24 @@ class TempMessageActionsDialog( binding.menuResendMessage.visibility = getVisibility(visible) } - private fun initMenuDeleteMessage() { - binding.menuDeleteMessage.setOnClickListener { - chatActivity.chatViewModel.deleteTempMessage(message) - dismiss() + private fun initMenuDeleteMessage(visible: Boolean) { + if (visible) { + binding.menuDeleteMessage.setOnClickListener { + chatActivity.chatViewModel.deleteTempMessage(message) + dismiss() + } } + binding.menuDeleteMessage.visibility = getVisibility(visible) } - private fun initMenuEditMessage() { - binding.menuEditMessage.setOnClickListener { - chatActivity.messageInputViewModel.edit(message) - dismiss() + private fun initMenuEditMessage(visible: Boolean) { + if (visible) { + binding.menuEditMessage.setOnClickListener { + chatActivity.messageInputViewModel.edit(message) + dismiss() + } } + binding.menuEditMessage.visibility = getVisibility(visible) } private fun initMenuItemCopy() { From aa5b4d028acc05705708c5b6263daa3327a1154c Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 6 Jan 2025 16:09:01 +0100 Subject: [PATCH 37/40] remove click listener for failed messages (use default longclick) Signed-off-by: Marcel Hibbe --- .../talk/adapters/messages/OutcomingTextMessageViewHolder.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index 652d3fc26..b1a19a0fa 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -128,9 +128,6 @@ class OutcomingTextMessageViewHolder(itemView: View) : R.drawable.baseline_report_problem_24, context.resources?.getString(R.string.nc_message_failed) ) - binding.bubble.setOnClickListener { - commonMessageInterface.onOpenMessageActionsDialog(message) - } } else if (message.isTemporary) { showSendingSpinner() } else if (message.readStatus == ReadStatus.READ) { From 68065d7e21ddc6db05c1e4bfd0442dd167bc1b31 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Mon, 6 Jan 2025 16:22:22 +0100 Subject: [PATCH 38/40] fix lint warning Signed-off-by: Marcel Hibbe --- .../java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt index 47d056b41..727ac8847 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt @@ -44,5 +44,5 @@ data class ChatMessageJson( @JsonField(name = ["lastEditTimestamp"]) var lastEditTimestamp: Long? = 0, @JsonField(name = ["deleted"]) var deleted: Boolean = false, @JsonField(name = ["referenceId"]) var referenceId: String? = null, - @JsonField(name = ["silent"]) var silent: Boolean = false, + @JsonField(name = ["silent"]) var silent: Boolean = false ) : Parcelable From cd096366c5bf4cc535d27694626a17fc3be2aee4 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Fri, 10 Jan 2025 12:07:27 +0100 Subject: [PATCH 39/40] change icons for message sending and messages sending failed remove shadowed var Signed-off-by: Marcel Hibbe --- .../messages/IncomingTextMessageViewHolder.kt | 1 - .../OutcomingTextMessageViewHolder.kt | 17 +++-------------- .../drawable/baseline_error_outline_24.xml | 16 ++++++++++++++++ .../res/drawable/baseline_schedule_24.xml | 19 +++++++++++++++++++ .../item_custom_outcoming_text_message.xml | 14 ++++---------- app/src/main/res/values/strings.xml | 1 + 6 files changed, 43 insertions(+), 25 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_error_outline_24.xml create mode 100644 app/src/main/res/drawable/baseline_schedule_24.xml diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt index b4883ffb2..9fab9306c 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingTextMessageViewHolder.kt @@ -218,7 +218,6 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) : ) binding.messageQuote.quotedChatMessageView.setOnClickListener { - val chatActivity = commonMessageInterface as ChatActivity chatActivity.jumpToQuotedMessage(parentChatMessage) } } catch (e: Exception) { diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt index b1a19a0fa..07ccb5cd3 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingTextMessageViewHolder.kt @@ -124,12 +124,9 @@ class OutcomingTextMessageViewHolder(itemView: View) : binding.sendingProgress.visibility = View.GONE if (message.sendingFailed) { - updateStatus( - R.drawable.baseline_report_problem_24, - context.resources?.getString(R.string.nc_message_failed) - ) + updateStatus(R.drawable.baseline_error_outline_24, context.resources?.getString(R.string.nc_message_failed)) } else if (message.isTemporary) { - showSendingSpinner() + updateStatus(R.drawable.baseline_schedule_24, context.resources?.getString(R.string.nc_message_sending)) } else if (message.readStatus == ReadStatus.READ) { updateStatus(R.drawable.ic_check_all, context.resources?.getString(R.string.nc_message_read)) } else if (message.readStatus == ReadStatus.SENT) { @@ -170,13 +167,6 @@ class OutcomingTextMessageViewHolder(itemView: View) : binding.checkMark.contentDescription = description } - private fun showSendingSpinner() { - binding.sendingProgress.visibility = View.VISIBLE - binding.checkMark.visibility = View.GONE - - viewThemeUtils.material.colorProgressBar(binding.sendingProgress) - } - private fun longClickOnReaction(chatMessage: ChatMessage) { commonMessageInterface.onLongClickReactions(chatMessage) } @@ -205,7 +195,7 @@ class OutcomingTextMessageViewHolder(itemView: View) : ).first() } - parentChatMessage!!.activeUser = message.activeUser + parentChatMessage.activeUser = message.activeUser parentChatMessage.imageUrl?.let { binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE binding.messageQuote.quotedMessageImage.load(it) { @@ -232,7 +222,6 @@ class OutcomingTextMessageViewHolder(itemView: View) : viewThemeUtils.talk.colorOutgoingQuoteBackground(binding.messageQuote.quoteColoredView) binding.messageQuote.quotedChatMessageView.setOnClickListener { - val chatActivity = commonMessageInterface as ChatActivity chatActivity.jumpToQuotedMessage(parentChatMessage) } } catch (e: Exception) { diff --git a/app/src/main/res/drawable/baseline_error_outline_24.xml b/app/src/main/res/drawable/baseline_error_outline_24.xml new file mode 100644 index 000000000..b040255b5 --- /dev/null +++ b/app/src/main/res/drawable/baseline_error_outline_24.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/baseline_schedule_24.xml b/app/src/main/res/drawable/baseline_schedule_24.xml new file mode 100644 index 000000000..c5334702b --- /dev/null +++ b/app/src/main/res/drawable/baseline_schedule_24.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/app/src/main/res/layout/item_custom_outcoming_text_message.xml b/app/src/main/res/layout/item_custom_outcoming_text_message.xml index a124f1931..711367f4b 100644 --- a/app/src/main/res/layout/item_custom_outcoming_text_message.xml +++ b/app/src/main/res/layout/item_custom_outcoming_text_message.xml @@ -95,22 +95,16 @@ app:tint="@color/high_emphasis_text" tools:src="@drawable/ic_warning_white"/> - + app:layout_alignSelf="center" + app:tint="@color/high_emphasis_text" + tools:src="@drawable/baseline_schedule_24"/> Message sent Offline Failed + Sending Failed to send message: Remote audio off Add attachment From 4f4ac5e77237e48219a35f76513951841ddd881d Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 10 Jan 2025 11:45:49 +0000 Subject: [PATCH 40/40] Analysis: update lint results to reflect reduced error/warning count Signed-off-by: github-actions --- scripts/analysis/lint-results.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/analysis/lint-results.txt b/scripts/analysis/lint-results.txt index bc7f6c076..7db741857 100644 --- a/scripts/analysis/lint-results.txt +++ b/scripts/analysis/lint-results.txt @@ -1,2 +1,2 @@ DO NOT TOUCH; GENERATED BY DRONE - Lint Report: 36 errors and 106 warnings + Lint Report: 36 errors and 104 warnings