diff --git a/.idea/inspectionProfiles/ktlint.xml b/.idea/inspectionProfiles/ktlint.xml
index 0938bfdc8f..18d15ddc14 100644
--- a/.idea/inspectionProfiles/ktlint.xml
+++ b/.idea/inspectionProfiles/ktlint.xml
@@ -39,5 +39,6 @@
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 99bfa46a20..fadc95c561 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -93,6 +93,12 @@ android {
buildConfigField "String", "PERMISSION_LOCAL_BROADCAST", "\"${localBroadcastPermission}\""
}
+ testOptions {
+ unitTests.all {
+ useJUnitPlatform()
+ }
+ }
+
buildTypes {
release {
minifyEnabled false
@@ -157,6 +163,7 @@ ext {
roomVersion = "2.6.1"
workVersion = "2.9.1"
espressoVersion = "3.6.1"
+ androidxTestVersion = "1.5.0"
media3_version = "1.4.0"
coroutines_version = "1.8.1"
mockitoKotlinVersion = "5.4.0"
@@ -170,10 +177,14 @@ configurations.configureEach {
}
dependencies {
+ spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.13.0'
+ spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.4'
+
implementation("androidx.compose.runtime:runtime:1.6.8")
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.datastore:datastore-core:1.1.1'
implementation 'androidx.datastore:datastore-preferences:1.1.1'
+ implementation 'androidx.test.ext:junit-ktx:1.1.5'
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.6")
implementation fileTree(include: ['*'], dir: 'libs')
@@ -192,7 +203,6 @@ dependencies {
implementation "androidx.work:work-runtime:${workVersion}"
implementation "androidx.work:work-rxjava2:${workVersion}"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
- androidTestImplementation "androidx.work:work-testing:${workVersion}"
implementation 'com.google.android.flexbox:flexbox:3.0.0'
implementation ('com.github.bitfireAT:dav4jvm:2.1.3', {
exclude group: 'org.ogce', module: 'xpp3' // Android comes with its own XmlPullParser
@@ -289,6 +299,12 @@ dependencies {
})
implementation 'androidx.core:core-ktx:1.13.1'
+ implementation 'androidx.activity:activity-ktx:1.9.0'
+ implementation 'com.github.nextcloud.android-common:ui:0.21.0'
+ implementation 'com.github.nextcloud-deps:android-talk-webrtc:121.6167.0'
+
+ gplayImplementation 'com.google.android.gms:play-services-base:18.4.0'
+ gplayImplementation "com.google.firebase:firebase-messaging:23.4.1"
//compose
implementation(platform("androidx.compose:compose-bom:2024.06.00"))
@@ -305,11 +321,14 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:5.12.0'
- androidTestImplementation 'org.mockito:mockito-android:5.12.0'
testImplementation 'androidx.arch.core:core-testing:2.2.0'
- androidTestImplementation "androidx.test:core:1.6.1"
+ androidTestImplementation "androidx.test:core:1.5.0"
+ androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1"
+ androidTestImplementation 'androidx.test:core-ktx:1.6.1'
+ androidTestImplementation 'org.mockito:mockito-android:5.12.0'
+ androidTestImplementation "androidx.work:work-testing:${workVersion}"
// Espresso core
androidTestImplementation ("androidx.test.espresso:espresso-core:$espressoVersion", {
exclude group: 'com.android.support', module: 'support-annotations'
@@ -317,6 +336,9 @@ dependencies {
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-web:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-accessibility:$espressoVersion"
+
+ androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
+
androidTestImplementation('com.android.support.test.espresso:espresso-intents:3.0.2')
spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.13.0'
@@ -325,7 +347,7 @@ dependencies {
gplayImplementation 'com.google.android.gms:play-services-base:18.5.0'
gplayImplementation "com.google.firebase:firebase-messaging:24.0.0"
- implementation 'androidx.activity:activity-ktx:1.9.1'
+ implementation 'androidx.activity:activity-ktx:1.9.1'
implementation 'com.github.nextcloud.android-common:ui:0.23.0'
diff --git a/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/10.json b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/10.json
index 08c6493c46..cdff3dfae2 100644
--- a/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/10.json
+++ b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/10.json
@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 10,
- "identityHash": "1b2dab0ea495c45c9c9ee6e64ba74039",
+ "identityHash": "c07a2543aa583e08e7b3208f44fcc7ac",
"entities": [
{
"tableName": "User",
@@ -135,12 +135,575 @@
},
"indices": [],
"foreignKeys": []
+ },
+ {
+ "tableName": "Conversations",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER NOT NULL, `token` TEXT, `name` TEXT, `displayName` TEXT, `description` TEXT, `type` TEXT, `lastPing` INTEGER NOT NULL, `participantType` TEXT, `hasPassword` INTEGER NOT NULL, `sessionId` TEXT, `actorId` TEXT, `actorType` TEXT, `isFavorite` INTEGER NOT NULL, `lastActivity` INTEGER NOT NULL, `unreadMessages` INTEGER NOT NULL, `unreadMention` INTEGER NOT NULL, `lastMessageJson` TEXT, `objectType` TEXT, `notificationLevel` TEXT, `readOnly` TEXT, `lobbyState` TEXT, `lobbyTimer` INTEGER, `lastReadMessage` INTEGER NOT NULL, `lastCommonReadMessage` INTEGER NOT NULL, `hasCall` INTEGER NOT NULL, `callFlag` INTEGER NOT NULL, `canStartCall` INTEGER NOT NULL, `canLeaveConversation` INTEGER, `canDeleteConversation` INTEGER, `unreadMentionDirect` INTEGER, `notificationCalls` INTEGER, `permissions` INTEGER NOT NULL, `messageExpiration` INTEGER NOT NULL, `status` TEXT, `statusIcon` TEXT, `statusMessage` TEXT, `statusClearAt` INTEGER, `callRecording` INTEGER NOT NULL, `avatarVersion` TEXT, `isCustomAvatar` INTEGER, `callStartTime` INTEGER, `recordingConsent` INTEGER NOT NULL, `remoteServer` TEXT, `remoteToken` TEXT, 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": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "displayName",
+ "columnName": "displayName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "description",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastPing",
+ "columnName": "lastPing",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "participantType",
+ "columnName": "participantType",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "hasPassword",
+ "columnName": "hasPassword",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sessionId",
+ "columnName": "sessionId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "actorId",
+ "columnName": "actorId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "actorType",
+ "columnName": "actorType",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "favorite",
+ "columnName": "isFavorite",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastActivity",
+ "columnName": "lastActivity",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unreadMessages",
+ "columnName": "unreadMessages",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "unreadMention",
+ "columnName": "unreadMention",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastMessageJson",
+ "columnName": "lastMessageJson",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "objectType",
+ "columnName": "objectType",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "notificationLevel",
+ "columnName": "notificationLevel",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "conversationReadOnlyState",
+ "columnName": "readOnly",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lobbyState",
+ "columnName": "lobbyState",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lobbyTimer",
+ "columnName": "lobbyTimer",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastReadMessage",
+ "columnName": "lastReadMessage",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastCommonReadMessage",
+ "columnName": "lastCommonReadMessage",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hasCall",
+ "columnName": "hasCall",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "callFlag",
+ "columnName": "callFlag",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "canStartCall",
+ "columnName": "canStartCall",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "canLeaveConversation",
+ "columnName": "canLeaveConversation",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "canDeleteConversation",
+ "columnName": "canDeleteConversation",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "unreadMentionDirect",
+ "columnName": "unreadMentionDirect",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "notificationCalls",
+ "columnName": "notificationCalls",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "permissions",
+ "columnName": "permissions",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "messageExpiration",
+ "columnName": "messageExpiration",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "status",
+ "columnName": "status",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "statusIcon",
+ "columnName": "statusIcon",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "statusMessage",
+ "columnName": "statusMessage",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "statusClearAt",
+ "columnName": "statusClearAt",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "callRecording",
+ "columnName": "callRecording",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "avatarVersion",
+ "columnName": "avatarVersion",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "hasCustomAvatar",
+ "columnName": "isCustomAvatar",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "callStartTime",
+ "columnName": "callStartTime",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "recordingConsentRequired",
+ "columnName": "recordingConsent",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "remoteServer",
+ "columnName": "remoteServer",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "remoteToken",
+ "columnName": "remoteToken",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "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, `token` TEXT, `id` INTEGER NOT NULL, `internalConversationId` TEXT, `actorType` TEXT, `actorId` TEXT, `actorDisplayName` TEXT, `timestamp` INTEGER NOT NULL, `systemMessage` TEXT, `messageType` TEXT, `isReplyable` INTEGER NOT NULL, `message` TEXT, `messageParameters` TEXT, `expirationTimestamp` INTEGER NOT NULL, `parent` INTEGER, `reactions` TEXT, `reactionsSelf` TEXT, `markdown` INTEGER, `lastEditActorType` TEXT, `lastEditActorId` TEXT, `lastEditActorDisplayName` TEXT, `lastEditTimestamp` INTEGER, `deleted` 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": false
+ },
+ {
+ "fieldPath": "token",
+ "columnName": "token",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "internalConversationId",
+ "columnName": "internalConversationId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "actorType",
+ "columnName": "actorType",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "actorId",
+ "columnName": "actorId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "actorDisplayName",
+ "columnName": "actorDisplayName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "timestamp",
+ "columnName": "timestamp",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "systemMessageType",
+ "columnName": "systemMessage",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "messageType",
+ "columnName": "messageType",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "replyable",
+ "columnName": "isReplyable",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "message",
+ "columnName": "message",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "messageParameters",
+ "columnName": "messageParameters",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "expirationTimestamp",
+ "columnName": "expirationTimestamp",
+ "affinity": "INTEGER",
+ "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": "renderMarkdown",
+ "columnName": "markdown",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastEditActorType",
+ "columnName": "lastEditActorType",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastEditActorId",
+ "columnName": "lastEditActorId",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastEditActorDisplayName",
+ "columnName": "lastEditActorDisplayName",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastEditTimestamp",
+ "columnName": "lastEditTimestamp",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "deleted",
+ "columnName": "deleted",
+ "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": [],
+ "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, '1b2dab0ea495c45c9c9ee6e64ba74039')"
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c07a2543aa583e08e7b3208f44fcc7ac')"
]
}
}
\ No newline at end of file
diff --git a/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/11.json b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/11.json
new file mode 100644
index 0000000000..6fa34ab2ef
--- /dev/null
+++ b/app/schemas/com.nextcloud.talk.data.source.local.TalkDatabase/11.json
@@ -0,0 +1,719 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 11,
+ "identityHash": "bc802cadfdef41d3eb94ffbb0729eb89",
+ "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, 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
+ }
+ ],
+ "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, `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": "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, 'bc802cadfdef41d3eb94ffbb0729eb89')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/nextcloud/talk/data/database/dao/ChatBlocksDaoTest.kt b/app/src/androidTest/java/com/nextcloud/talk/data/database/dao/ChatBlocksDaoTest.kt
new file mode 100644
index 0000000000..f832a97d53
--- /dev/null
+++ b/app/src/androidTest/java/com/nextcloud/talk/data/database/dao/ChatBlocksDaoTest.kt
@@ -0,0 +1,233 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.database.dao
+
+import android.content.Context
+import androidx.room.Room
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.runner.AndroidJUnit4
+import com.nextcloud.talk.data.database.model.ChatBlockEntity
+import com.nextcloud.talk.data.database.model.ConversationEntity
+import com.nextcloud.talk.data.source.local.TalkDatabase
+import com.nextcloud.talk.data.user.UsersDao
+import com.nextcloud.talk.data.user.model.UserEntity
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
+import com.nextcloud.talk.models.json.participants.Participant
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ChatBlocksDaoTest {
+ private lateinit var usersDao: UsersDao
+ private lateinit var conversationsDao: ConversationsDao
+ private lateinit var chatBlocksDao: ChatBlocksDao
+ private lateinit var db: TalkDatabase
+ private val tag = ChatBlocksDaoTest::class.java.simpleName
+
+ @Before
+ fun createDb() {
+ val context = ApplicationProvider.getApplicationContext()
+ db = Room.inMemoryDatabaseBuilder(
+ context,
+ TalkDatabase::class.java
+ ).build()
+ usersDao = db.usersDao()
+ conversationsDao = db.conversationsDao()
+ chatBlocksDao = db.chatBlocksDao()
+ }
+
+ @After
+ fun closeDb() = db.close()
+
+ @Test
+ fun testGetConnectedChatBlocks() =
+ runTest {
+
+ usersDao.saveUser(createUserEntity("account1", "Account 1"))
+ val account1 = usersDao.getUserWithUserId("account1").blockingGet()
+
+ conversationsDao.upsertConversations(
+ listOf(
+ createConversationEntity(
+ accountId = account1.id,
+ "abc",
+ roomName = "Conversation One"
+ ),
+ createConversationEntity(
+ accountId = account1.id,
+ "def",
+ roomName = "Conversation Two"
+ ),
+ )
+ )
+
+ val conversation1 = conversationsDao.getConversationsForUser(account1.id).first()[0]
+ val conversation2 = conversationsDao.getConversationsForUser(account1.id).first()[1]
+
+ val searchedChatBlock = ChatBlockEntity(
+ internalConversationId = conversation1.internalId,
+ accountId = conversation1.accountId,
+ token = conversation1.token,
+ oldestMessageId = 50,
+ newestMessageId = 60,
+ hasHistory = true
+ )
+
+ val chatBlockTooOld = ChatBlockEntity(
+ internalConversationId = conversation1.internalId,
+ accountId = conversation1.accountId,
+ token = conversation1.token,
+ oldestMessageId = 10,
+ newestMessageId = 20,
+ hasHistory = true
+ )
+
+ val chatBlockOverlap1 = ChatBlockEntity(
+ internalConversationId = conversation1.internalId,
+ accountId = conversation1.accountId,
+ token = conversation1.token,
+ oldestMessageId = 45,
+ newestMessageId = 55,
+ hasHistory = true
+ )
+
+ val chatBlockWithin = ChatBlockEntity(
+ internalConversationId = conversation1.internalId,
+ accountId = conversation1.accountId,
+ token = conversation1.token,
+ oldestMessageId = 52,
+ newestMessageId = 58,
+ hasHistory = true
+ )
+
+ val chatBlockOverall = ChatBlockEntity(
+ internalConversationId = conversation1.internalId,
+ accountId = conversation1.accountId,
+ token = conversation1.token,
+ oldestMessageId = 1,
+ newestMessageId = 99,
+ hasHistory = true
+ )
+
+ val chatBlockOverlap2 = ChatBlockEntity(
+ internalConversationId = conversation1.internalId,
+ accountId = conversation1.accountId,
+ token = conversation1.token,
+ oldestMessageId = 59,
+ newestMessageId = 70,
+ hasHistory = true
+ )
+
+ val chatBlockTooNew = ChatBlockEntity(
+ internalConversationId = conversation1.internalId,
+ accountId = conversation1.accountId,
+ token = conversation1.token,
+ oldestMessageId = 80,
+ newestMessageId = 90,
+ hasHistory = true
+ )
+
+ val chatBlockWithinButOtherConversation = ChatBlockEntity(
+ internalConversationId = conversation2.internalId,
+ accountId = conversation2.accountId,
+ token = conversation2.token,
+ oldestMessageId = 53,
+ newestMessageId = 57,
+ hasHistory = true
+ )
+
+ chatBlocksDao.upsertChatBlock(searchedChatBlock)
+
+ chatBlocksDao.upsertChatBlock(chatBlockTooOld)
+ chatBlocksDao.upsertChatBlock(chatBlockOverlap1)
+ chatBlocksDao.upsertChatBlock(chatBlockWithin)
+ chatBlocksDao.upsertChatBlock(chatBlockOverall)
+ chatBlocksDao.upsertChatBlock(chatBlockOverlap2)
+ chatBlocksDao.upsertChatBlock(chatBlockTooNew)
+ chatBlocksDao.upsertChatBlock(chatBlockWithinButOtherConversation)
+
+ val results = chatBlocksDao.getConnectedChatBlocks(
+ conversation1.internalId,
+ searchedChatBlock.oldestMessageId,
+ searchedChatBlock.newestMessageId
+ )
+
+ assertEquals(5, results.first().size)
+ }
+
+ private fun createUserEntity(userId: String, userName: String) =
+ UserEntity(
+ userId = userId,
+ username = userName,
+ baseUrl = null,
+ token = null,
+ displayName = null,
+ pushConfigurationState = null,
+ capabilities = null,
+ serverVersion = null,
+ clientCertificate = null,
+ externalSignalingServer = null,
+ current = java.lang.Boolean.FALSE,
+ scheduledForDeletion = java.lang.Boolean.FALSE
+ )
+
+ private fun createConversationEntity(accountId: Long, token: String, roomName: String): ConversationEntity {
+ return ConversationEntity(
+ internalId = "$accountId@$token",
+ accountId = accountId,
+ token = token,
+ name = roomName,
+ actorId = "",
+ actorType = "",
+ messageExpiration = 0,
+ unreadMessages = 0,
+ statusMessage = null,
+ lastMessage = null,
+ canDeleteConversation = false,
+ canLeaveConversation = false,
+ lastCommonReadMessage = 0,
+ lastReadMessage = 0,
+ type = ConversationEnums.ConversationType.DUMMY,
+ status = "",
+ callFlag = 1,
+ favorite = false,
+ lastPing = 0,
+ hasCall = false,
+ sessionId = "",
+ canStartCall = false,
+ lastActivity = 0,
+ remoteServer = "",
+ avatarVersion = "",
+ unreadMentionDirect = false,
+ callRecording = 1,
+ callStartTime = 0,
+ statusClearAt = 0,
+ unreadMention = false,
+ lobbyState = ConversationEnums.LobbyState.LOBBY_STATE_MODERATORS_ONLY,
+ lobbyTimer = 0,
+ objectType = ConversationEnums.ObjectType.FILE,
+ statusIcon = null,
+ description = "",
+ displayName = "",
+ hasPassword = false,
+ permissions = 0,
+ notificationCalls = 0,
+ remoteToken = "",
+ notificationLevel = ConversationEnums.NotificationLevel.ALWAYS,
+ conversationReadOnlyState = ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_ONLY,
+ hasCustomAvatar = false,
+ participantType = Participant.ParticipantType.DUMMY,
+ recordingConsentRequired = 1
+ )
+ }
+}
diff --git a/app/src/androidTest/java/com/nextcloud/talk/data/database/dao/ChatMessagesDaoTest.kt b/app/src/androidTest/java/com/nextcloud/talk/data/database/dao/ChatMessagesDaoTest.kt
new file mode 100644
index 0000000000..7b3f6876d3
--- /dev/null
+++ b/app/src/androidTest/java/com/nextcloud/talk/data/database/dao/ChatMessagesDaoTest.kt
@@ -0,0 +1,269 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.database.dao
+
+import android.content.Context
+import android.util.Log
+import androidx.room.Room
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.runner.AndroidJUnit4
+import com.nextcloud.talk.chat.data.model.ChatMessage
+import com.nextcloud.talk.data.database.model.ChatMessageEntity
+import com.nextcloud.talk.data.database.model.ConversationEntity
+import com.nextcloud.talk.data.source.local.TalkDatabase
+import com.nextcloud.talk.data.user.UsersDao
+import com.nextcloud.talk.data.user.model.UserEntity
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
+import com.nextcloud.talk.models.json.participants.Participant
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ChatMessagesDaoTest {
+
+ private lateinit var usersDao: UsersDao
+ private lateinit var conversationsDao: ConversationsDao
+ private lateinit var chatMessagesDao: ChatMessagesDao
+ private lateinit var db: TalkDatabase
+ private val tag = ChatMessagesDaoTest::class.java.simpleName
+
+ var chatMessageCounter: Long = 1
+
+ @Before
+ fun createDb() {
+ val context = ApplicationProvider.getApplicationContext()
+ db = Room.inMemoryDatabaseBuilder(
+ context,
+ TalkDatabase::class.java
+ ).build()
+ usersDao = db.usersDao()
+ conversationsDao = db.conversationsDao()
+ chatMessagesDao = db.chatMessagesDao()
+ }
+
+ @After
+ fun closeDb() = db.close()
+
+ @Test
+ fun test() =
+ runTest {
+ usersDao.saveUser(createUserEntity("account1", "Account 1"))
+ usersDao.saveUser(createUserEntity("account2", "Account 2"))
+
+ val account1 = usersDao.getUserWithUserId("account1").blockingGet()
+ val account2 = usersDao.getUserWithUserId("account2").blockingGet()
+
+ // Problem: lets say we want to update the conv list -> We don#t know the primary keys!
+ // with account@token that would be easier!
+ conversationsDao.upsertConversations(
+ listOf(
+ createConversationEntity(
+ accountId = account1.id,
+ roomName = "Conversation One"
+ ),
+ createConversationEntity(
+ accountId = account1.id,
+ roomName = "Conversation Two"
+ ),
+ createConversationEntity(
+ accountId = account2.id,
+ roomName = "Conversation Three"
+ )
+ )
+ )
+
+ assertEquals(2, conversationsDao.getConversationsForUser(account1.id).first().size)
+ assertEquals(1, conversationsDao.getConversationsForUser(account2.id).first().size)
+
+ // Lets imagine we are on conversations screen...
+ conversationsDao.getConversationsForUser(account1.id).first().forEach {
+ Log.d(tag, "- next Conversation for account1 -")
+ Log.d(tag, "internalId (PK): " + it.internalId)
+ Log.d(tag, "accountId: " + it.accountId)
+ Log.d(tag, "name: " + it.name)
+ Log.d(tag, "token: " + it.token)
+ }
+
+ // User sees all conversations and clicks on a item. That's how we get a conversation
+ val conversation1 = conversationsDao.getConversationsForUser(account1.id).first()[0]
+ val conversation2 = conversationsDao.getConversationsForUser(account1.id).first()[1]
+
+ // Having a conversation token, we can also get a conversation directly
+ val conversation1GotByToken = conversationsDao.getConversationForUser(
+ account1.id,
+ conversation1.token!!
+ ).first()
+
+ assertEquals(conversation1, conversation1GotByToken)
+
+ // Lets insert some messages to the conversations
+ chatMessagesDao.upsertChatMessages(
+ listOf(
+ createChatMessageEntity(conversation1.internalId, "hello"),
+ createChatMessageEntity(conversation1.internalId, "here"),
+ createChatMessageEntity(conversation1.internalId, "are"),
+ createChatMessageEntity(conversation1.internalId, "some"),
+ createChatMessageEntity(conversation1.internalId, "messages")
+ )
+ )
+ chatMessagesDao.upsertChatMessages(
+ listOf(
+ createChatMessageEntity(conversation2.internalId, "first message in conversation 2")
+ )
+ )
+
+ chatMessagesDao.getMessagesForConversation(conversation1.internalId).first().forEach {
+ Log.d(tag, "- next Message for conversation1 (account1)-")
+ Log.d(tag, "id (PK): " + it.id)
+ Log.d(tag, "message: " + it.message)
+ }
+
+ val chatMessagesConv1 = chatMessagesDao.getMessagesForConversation(conversation1.internalId)
+ assertEquals(5, chatMessagesConv1.first().size)
+
+ val chatMessagesConv2 = chatMessagesDao.getMessagesForConversation(conversation2.internalId)
+ assertEquals(1, chatMessagesConv2.first().size)
+
+ assertEquals("some", chatMessagesConv1.first()[1].message)
+
+ val conv1chatMessage3 = chatMessagesDao.getChatMessageForConversation(conversation1.internalId, 3).first()
+ assertEquals("are", conv1chatMessage3.message)
+
+ val chatMessagesConv1Since =
+ chatMessagesDao.getMessagesForConversationSince(conversation1.internalId, conv1chatMessage3.id)
+ assertEquals(3, chatMessagesConv1Since.first().size)
+ assertEquals("are", chatMessagesConv1Since.first()[0].message)
+ assertEquals("some", chatMessagesConv1Since.first()[1].message)
+ assertEquals("messages", chatMessagesConv1Since.first()[2].message)
+
+ val chatMessagesConv1To =
+ chatMessagesDao.getMessagesForConversationBeforeAndEqual(
+ conversation1.internalId,
+ conv1chatMessage3.id,
+ 3
+ )
+ assertEquals(3, chatMessagesConv1To.first().size)
+ assertEquals("hello", chatMessagesConv1To.first()[2].message)
+ assertEquals("here", chatMessagesConv1To.first()[1].message)
+ assertEquals("are", chatMessagesConv1To.first()[0].message)
+ }
+
+ private fun createUserEntity(userId: String, userName: String) =
+ UserEntity(
+ userId = userId,
+ username = userName,
+ baseUrl = null,
+ token = null,
+ displayName = null,
+ pushConfigurationState = null,
+ capabilities = null,
+ serverVersion = null,
+ clientCertificate = null,
+ externalSignalingServer = null,
+ current = java.lang.Boolean.FALSE,
+ scheduledForDeletion = java.lang.Boolean.FALSE
+ )
+
+ private fun createConversationEntity(accountId: Long, roomName: String): ConversationEntity {
+ val token = (0..10000000).random().toString()
+
+ return ConversationEntity(
+ internalId = "$accountId@$token",
+ accountId = accountId,
+ token = token,
+ name = roomName,
+ actorId = "",
+ actorType = "",
+ messageExpiration = 0,
+ unreadMessages = 0,
+ statusMessage = null,
+ lastMessage = null,
+ canDeleteConversation = false,
+ canLeaveConversation = false,
+ lastCommonReadMessage = 0,
+ lastReadMessage = 0,
+ type = ConversationEnums.ConversationType.DUMMY,
+ status = "",
+ callFlag = 1,
+ favorite = false,
+ lastPing = 0,
+ hasCall = false,
+ sessionId = "",
+ canStartCall = false,
+ lastActivity = 0,
+ remoteServer = "",
+ avatarVersion = "",
+ unreadMentionDirect = false,
+ callRecording = 1,
+ callStartTime = 0,
+ statusClearAt = 0,
+ unreadMention = false,
+ lobbyState = ConversationEnums.LobbyState.LOBBY_STATE_MODERATORS_ONLY,
+ lobbyTimer = 0,
+ objectType = ConversationEnums.ObjectType.FILE,
+ statusIcon = null,
+ description = "",
+ displayName = "",
+ hasPassword = false,
+ permissions = 0,
+ notificationCalls = 0,
+ remoteToken = "",
+ notificationLevel = ConversationEnums.NotificationLevel.ALWAYS,
+ conversationReadOnlyState = ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_ONLY,
+ hasCustomAvatar = false,
+ participantType = Participant.ParticipantType.DUMMY,
+ recordingConsentRequired = 1
+ )
+ }
+
+ 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@$id",
+ internalConversationId = internalConversationId,
+ id = id,
+ 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
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
index d14f3c4d7a..f0b746be0f 100644
--- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
@@ -1734,7 +1734,7 @@ class CallActivity : CallBaseActivity() {
private fun setInitialApplicationWideCurrentRoomHolderValues(conversation: Conversation) {
ApplicationWideCurrentRoomHolder.getInstance().userInRoom = conversationUser
ApplicationWideCurrentRoomHolder.getInstance().session = conversation.sessionId
- ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = conversation.roomId
+ // ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = conversation.roomId
ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken = conversation.token
ApplicationWideCurrentRoomHolder.getInstance().callStartTime = conversation.callStartTime
}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt
index bf14068469..c3b2af79f3 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ConversationItem.kt
@@ -22,21 +22,21 @@ import androidx.core.content.res.ResourcesCompat
import com.nextcloud.talk.R
import com.nextcloud.talk.adapters.items.ConversationItem.ConversationItemViewHolder
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.chat.data.model.ChatMessage.MessageType
+import com.nextcloud.talk.data.database.mappers.asModel
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.RvItemConversationWithLastMessageBinding
import com.nextcloud.talk.extensions.loadConversationAvatar
import com.nextcloud.talk.extensions.loadNoteToSelfAvatar
import com.nextcloud.talk.extensions.loadSystemAvatar
import com.nextcloud.talk.extensions.loadUserAvatar
-import com.nextcloud.talk.models.json.chat.ChatMessage
-import com.nextcloud.talk.models.json.conversations.Conversation
-import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType
+import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.ui.StatusDrawable
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
-import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.DisplayUtils
-
+import com.nextcloud.talk.utils.SpreedFeatures
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFilterable
@@ -46,7 +46,7 @@ import eu.davidea.viewholders.FlexibleViewHolder
import java.util.regex.Pattern
class ConversationItem(
- val model: Conversation,
+ val model: ConversationModel,
private val user: User,
private val context: Context,
private val viewThemeUtils: ViewThemeUtils
@@ -54,9 +54,10 @@ class ConversationItem(
ISectionable,
IFilterable {
private var header: GenericTextHeaderItem? = null
+ private val chatMessage = model.lastMessage?.asModel()
constructor(
- conversation: Conversation,
+ conversation: ConversationModel,
user: User,
activityContext: Context,
genericTextHeaderItem: GenericTextHeaderItem?,
@@ -127,7 +128,7 @@ class ConversationItem(
} else {
holder.binding.favoriteConversationImageView.visibility = View.GONE
}
- if (ConversationType.ROOM_SYSTEM !== model.type) {
+ if (ConversationEnums.ConversationType.ROOM_SYSTEM !== model.type) {
val size = DisplayUtils.convertDpToPixel(STATUS_SIZE_IN_DP, appContext)
holder.binding.userStatusImage.visibility = View.VISIBLE
holder.binding.userStatusImage.setImageDrawable(
@@ -149,13 +150,13 @@ class ConversationItem(
private fun showAvatar(holder: ConversationItemViewHolder) {
holder.binding.dialogAvatar.visibility = View.VISIBLE
var shouldLoadAvatar = shouldLoadAvatar(holder)
- if (ConversationType.ROOM_SYSTEM == model.type) {
+ if (ConversationEnums.ConversationType.ROOM_SYSTEM == model.type) {
holder.binding.dialogAvatar.loadSystemAvatar()
shouldLoadAvatar = false
}
if (shouldLoadAvatar) {
when (model.type) {
- ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> {
+ ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> {
if (!TextUtils.isEmpty(model.name)) {
holder.binding.dialogAvatar.loadUserAvatar(
user,
@@ -168,11 +169,12 @@ class ConversationItem(
}
}
- ConversationType.ROOM_GROUP_CALL,
- ConversationType.FORMER_ONE_TO_ONE,
- ConversationType.ROOM_PUBLIC_CALL ->
+ ConversationEnums.ConversationType.ROOM_GROUP_CALL,
+ ConversationEnums.ConversationType.FORMER_ONE_TO_ONE,
+ ConversationEnums.ConversationType.ROOM_PUBLIC_CALL ->
holder.binding.dialogAvatar.loadConversationAvatar(user, model, false, viewThemeUtils)
- ConversationType.NOTE_TO_SELF ->
+
+ ConversationEnums.ConversationType.NOTE_TO_SELF ->
holder.binding.dialogAvatar.loadNoteToSelfAvatar()
else -> holder.binding.dialogAvatar.visibility = View.GONE
@@ -182,7 +184,7 @@ class ConversationItem(
private fun shouldLoadAvatar(holder: ConversationItemViewHolder): Boolean {
return when (model.objectType) {
- Conversation.ObjectType.SHARE_PASSWORD -> {
+ ConversationEnums.ObjectType.SHARE_PASSWORD -> {
holder.binding.dialogAvatar.setImageDrawable(
ContextCompat.getDrawable(
context,
@@ -192,7 +194,7 @@ class ConversationItem(
false
}
- Conversation.ObjectType.FILE -> {
+ ConversationEnums.ObjectType.FILE -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
holder.binding.dialogAvatar.loadUserAvatar(
viewThemeUtils.talk.themePlaceholderAvatar(
@@ -213,7 +215,7 @@ class ConversationItem(
}
private fun setLastMessage(holder: ConversationItemViewHolder, appContext: Context) {
- if (model.lastMessage != null) {
+ if (chatMessage != null) {
holder.binding.dialogDate.visibility = View.VISIBLE
holder.binding.dialogDate.text = DateUtils.getRelativeTimeSpanString(
model.lastActivity * MILLIES,
@@ -221,20 +223,20 @@ class ConversationItem(
0,
DateUtils.FORMAT_ABBREV_RELATIVE
)
- if (!TextUtils.isEmpty(model.lastMessage!!.systemMessage) ||
- ConversationType.ROOM_SYSTEM === model.type
+ if (!TextUtils.isEmpty(chatMessage?.systemMessage) ||
+ ConversationEnums.ConversationType.ROOM_SYSTEM === model.type
) {
- holder.binding.dialogLastMessage.text = model.lastMessage!!.text
+ holder.binding.dialogLastMessage.text = chatMessage?.text
} else {
- model.lastMessage!!.activeUser = user
+ chatMessage?.activeUser = user
val text =
if (
- model.lastMessage!!.getCalculateMessageType() === ChatMessage.MessageType.REGULAR_TEXT_MESSAGE
+ chatMessage?.messageType === MessageType.REGULAR_TEXT_MESSAGE.toString()
) {
calculateRegularLastMessageText(appContext)
} else {
- model.lastMessage!!.lastMessageDisplayText
+ lastMessageDisplayText
}
holder.binding.dialogLastMessage.text = text
}
@@ -245,16 +247,16 @@ class ConversationItem(
}
private fun calculateRegularLastMessageText(appContext: Context): String {
- return if (model.lastMessage!!.actorId == user.userId) {
+ return if (chatMessage?.actorId == user.userId) {
String.format(
appContext.getString(R.string.nc_formatted_message_you),
- model.lastMessage!!.lastMessageDisplayText
+ lastMessageDisplayText
)
} else {
val authorDisplayName =
- if (!TextUtils.isEmpty(model.lastMessage!!.actorDisplayName)) {
- model.lastMessage!!.actorDisplayName
- } else if ("guests" == model.lastMessage!!.actorType) {
+ if (!TextUtils.isEmpty(chatMessage?.actorDisplayName)) {
+ chatMessage?.actorDisplayName
+ } else if ("guests" == chatMessage?.actorType) {
appContext.getString(R.string.nc_guest)
} else {
""
@@ -262,7 +264,7 @@ class ConversationItem(
String.format(
appContext.getString(R.string.nc_formatted_message),
authorDisplayName,
- model.lastMessage!!.lastMessageDisplayText
+ lastMessageDisplayText
)
}
}
@@ -286,7 +288,7 @@ class ConversationItem(
context,
R.color.conversation_unread_bubble_text
)
- if (model.type === ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
+ if (model.type === ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
viewThemeUtils.material.colorChipBackground(holder.binding.dialogUnreadBubble)
} else if (model.unreadMention) {
if (hasSpreedFeatureCapability(user.capabilities?.spreedCapability!!, SpreedFeatures.DIRECT_MENTION_FLAG)) {
@@ -323,6 +325,94 @@ class ConversationItem(
this.header = header
}
+ private val lastMessageDisplayText: String
+ get() {
+ if (chatMessage?.getCalculateMessageType() == MessageType.REGULAR_TEXT_MESSAGE ||
+ chatMessage?.getCalculateMessageType() == MessageType.SYSTEM_MESSAGE ||
+ chatMessage?.getCalculateMessageType() == MessageType.SINGLE_LINK_MESSAGE
+ ) {
+ return chatMessage.text
+ } else {
+ if (MessageType.SINGLE_LINK_GIPHY_MESSAGE == chatMessage?.getCalculateMessageType() ||
+ MessageType.SINGLE_LINK_TENOR_MESSAGE == chatMessage?.getCalculateMessageType() ||
+ MessageType.SINGLE_LINK_GIF_MESSAGE == chatMessage?.getCalculateMessageType()
+ ) {
+ return if (chatMessage?.actorId == chatMessage?.activeUser!!.userId) {
+ sharedApplication!!.getString(R.string.nc_sent_a_gif_you)
+ } else {
+ String.format(
+ sharedApplication!!.resources.getString(R.string.nc_sent_a_gif),
+ chatMessage?.getNullsafeActorDisplayName()
+ )
+ }
+ } else if (MessageType.SINGLE_NC_ATTACHMENT_MESSAGE == chatMessage?.getCalculateMessageType()) {
+ return if (chatMessage?.actorId == chatMessage?.activeUser!!.userId) {
+ sharedApplication!!.getString(R.string.nc_sent_an_attachment_you)
+ } else {
+ String.format(
+ sharedApplication!!.resources.getString(R.string.nc_sent_an_attachment),
+ chatMessage?.getNullsafeActorDisplayName()
+ )
+ }
+ } else if (MessageType.SINGLE_NC_GEOLOCATION_MESSAGE == chatMessage?.getCalculateMessageType()) {
+ return if (chatMessage?.actorId == chatMessage?.activeUser!!.userId) {
+ sharedApplication!!.getString(R.string.nc_sent_location_you)
+ } else {
+ String.format(
+ sharedApplication!!.resources.getString(R.string.nc_sent_location),
+ chatMessage?.getNullsafeActorDisplayName()
+ )
+ }
+ } else if (MessageType.VOICE_MESSAGE == chatMessage?.getCalculateMessageType()) {
+ return if (chatMessage?.actorId == chatMessage?.activeUser!!.userId) {
+ sharedApplication!!.getString(R.string.nc_sent_voice_you)
+ } else {
+ String.format(
+ sharedApplication!!.resources.getString(R.string.nc_sent_voice),
+ chatMessage?.getNullsafeActorDisplayName()
+ )
+ }
+ } else if (MessageType.SINGLE_LINK_AUDIO_MESSAGE == chatMessage?.getCalculateMessageType()) {
+ return if (chatMessage?.actorId == chatMessage?.activeUser!!.userId) {
+ sharedApplication!!.getString(R.string.nc_sent_an_audio_you)
+ } else {
+ String.format(
+ sharedApplication!!.resources.getString(R.string.nc_sent_an_audio),
+ chatMessage?.getNullsafeActorDisplayName()
+ )
+ }
+ } else if (MessageType.SINGLE_LINK_VIDEO_MESSAGE == chatMessage?.getCalculateMessageType()) {
+ return if (chatMessage?.actorId == chatMessage?.activeUser!!.userId) {
+ sharedApplication!!.getString(R.string.nc_sent_a_video_you)
+ } else {
+ String.format(
+ sharedApplication!!.resources.getString(R.string.nc_sent_a_video),
+ chatMessage?.getNullsafeActorDisplayName()
+ )
+ }
+ } else if (MessageType.SINGLE_LINK_IMAGE_MESSAGE == chatMessage?.getCalculateMessageType()) {
+ return if (chatMessage?.actorId == chatMessage?.activeUser!!.userId) {
+ sharedApplication!!.getString(R.string.nc_sent_an_image_you)
+ } else {
+ String.format(
+ sharedApplication!!.resources.getString(R.string.nc_sent_an_image),
+ chatMessage?.getNullsafeActorDisplayName()
+ )
+ }
+ } else if (MessageType.POLL_MESSAGE == chatMessage?.getCalculateMessageType()) {
+ return if (chatMessage?.actorId == chatMessage?.activeUser!!.userId) {
+ sharedApplication!!.getString(R.string.nc_sent_poll_you)
+ } else {
+ String.format(
+ sharedApplication!!.resources.getString(R.string.nc_sent_poll),
+ chatMessage?.getNullsafeActorDisplayName()
+ )
+ }
+ }
+ }
+ return ""
+ }
+
class ConversationItemViewHolder(view: View?, adapter: FlexibleAdapter<*>?) : FlexibleViewHolder(view, adapter) {
var binding: RvItemConversationWithLastMessageBinding
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt
index 446456ac8a..c726c64e93 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/CallStartedViewHolder.kt
@@ -16,7 +16,7 @@ import coil.target.Target
import coil.transform.CircleCropTransformation
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.databinding.CallStartedMessageBinding
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/CommonMessageInterface.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/CommonMessageInterface.kt
index bad6960d3f..262ca69a8f 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/CommonMessageInterface.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/CommonMessageInterface.kt
@@ -6,7 +6,7 @@
*/
package com.nextcloud.talk.adapters.messages
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
interface CommonMessageInterface {
fun onLongClickReactions(chatMessage: ChatMessage)
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt
index 9f2117c913..e7a287f054 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLinkPreviewMessageViewHolder.kt
@@ -10,26 +10,35 @@ package com.nextcloud.talk.adapters.messages
import android.annotation.SuppressLint
import android.content.Context
import android.text.TextUtils
+import android.util.Log
import android.view.View
import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector
import coil.load
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R
+import com.nextcloud.talk.adapters.messages.OutcomingPollMessageViewHolder.Companion
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.databinding.ItemCustomIncomingLinkPreviewMessageBinding
import com.nextcloud.talk.extensions.loadBotsAvatar
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
+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)
@@ -168,40 +177,62 @@ class IncomingLinkPreviewMessageViewHolder(incomingView: View, payload: Any) :
}
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
- if (!message.isDeleted && message.parentMessage != null) {
- val parentChatMessage = message.parentMessage
- parentChatMessage!!.activeUser = message.activeUser
- parentChatMessage.imageUrl?.let {
- binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
- binding.messageQuote.quotedMessageImage.load(it) {
- addHeader(
- "Authorization",
- ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
+ if (message.parentMessageId != null && !message.isDeleted) {
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ val chatActivity = commonMessageInterface 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
+ 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,
+ true,
+ viewThemeUtils
+ )
+
+ binding.messageQuote.quotedMessageAuthor
+ .setTextColor(ContextCompat.getColor(context, R.color.textColorMaxContrast))
+
+ if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
+ viewThemeUtils.platform.colorViewBackground(
+ binding.messageQuote.quoteColoredView,
+ ColorRole.PRIMARY
+ )
+ } else {
+ binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
+ }
+
+ binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
+ } catch (e: Exception) {
+ Log.d(TAG, "Error when processing parent message in view holder", e)
}
- } ?: 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,
- true,
- viewThemeUtils
- )
-
- binding.messageQuote.quotedMessageAuthor
- .setTextColor(ContextCompat.getColor(context, R.color.textColorMaxContrast))
-
- if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
- viewThemeUtils.platform.colorViewBackground(binding.messageQuote.quoteColoredView, ColorRole.PRIMARY)
- } else {
- binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
}
-
- binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
} else {
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt
index f6d13bd029..f2ff49947f 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingLocationMessageViewHolder.kt
@@ -20,18 +20,21 @@ import android.view.MotionEvent
import android.view.View
import android.webkit.WebView
import android.webkit.WebViewClient
+import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector
import coil.load
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R
+import com.nextcloud.talk.adapters.messages.IncomingPollMessageViewHolder.Companion
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.databinding.ItemCustomIncomingLocationMessageBinding
import com.nextcloud.talk.extensions.loadBotsAvatar
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DateUtils
@@ -39,6 +42,11 @@ import com.nextcloud.talk.utils.UriUtils
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.net.URLEncoder
import javax.inject.Inject
@@ -150,40 +158,62 @@ class IncomingLocationMessageViewHolder(incomingView: View, payload: Any) :
}
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
- if (!message.isDeleted && message.parentMessage != null) {
- val parentChatMessage = message.parentMessage
- parentChatMessage!!.activeUser = message.activeUser
- parentChatMessage.imageUrl?.let {
- binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
- binding.messageQuote.quotedMessageImage.load(it) {
- addHeader(
- "Authorization",
- ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
+ if (message.parentMessageId != null && !message.isDeleted) {
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ val chatActivity = commonMessageInterface 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
+ 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,
+ true,
+ viewThemeUtils
+ )
+
+ binding.messageQuote.quotedMessageAuthor
+ .setTextColor(context.resources.getColor(R.color.textColorMaxContrast, null))
+
+ if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
+ viewThemeUtils.platform.colorViewBackground(
+ binding.messageQuote.quoteColoredView,
+ ColorRole.PRIMARY
+ )
+ } else {
+ binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
+ }
+
+ binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
+ } catch (e: Exception) {
+ Log.d(TAG, "Error when processing parent message in view holder", e)
}
- } ?: 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,
- true,
- viewThemeUtils
- )
-
- binding.messageQuote.quotedMessageAuthor
- .setTextColor(context.resources.getColor(R.color.textColorMaxContrast, null))
-
- if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
- viewThemeUtils.platform.colorViewBackground(binding.messageQuote.quoteColoredView, ColorRole.PRIMARY)
- } else {
- binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
}
-
- binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
} else {
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt
index 18cbb2c45c..3e35fe070c 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPollMessageViewHolder.kt
@@ -9,12 +9,15 @@ package com.nextcloud.talk.adapters.messages
import android.annotation.SuppressLint
import android.content.Context
import android.text.TextUtils
+import android.util.Log
import android.view.View
import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector
import coil.load
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R
+import com.nextcloud.talk.adapters.messages.IncomingTextMessageViewHolder.Companion
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
@@ -23,7 +26,7 @@ import com.nextcloud.talk.databinding.ItemCustomIncomingPollMessageBinding
import com.nextcloud.talk.extensions.loadBotsAvatar
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.polls.ui.PollMainDialogFragment
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
@@ -31,6 +34,11 @@ import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
+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)
@@ -176,40 +184,61 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) :
}
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
- if (!message.isDeleted && message.parentMessage != null) {
- val parentChatMessage = message.parentMessage
- parentChatMessage!!.activeUser = message.activeUser
- parentChatMessage.imageUrl?.let {
- binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
- binding.messageQuote.quotedMessageImage.load(it) {
- addHeader(
- "Authorization",
- ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
+ if (message.parentMessageId != null && !message.isDeleted) {
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ val chatActivity = commonMessageInterface as ChatActivity
+ val urlForChatting = ApiUtils.getUrlForChat(
+ chatActivity.chatApiVersion,
+ chatActivity.conversationUser?.baseUrl,
+ chatActivity.roomToken
)
- }
- } ?: 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,
- true,
- viewThemeUtils
- )
-
- binding.messageQuote.quotedMessageAuthor
- .setTextColor(ContextCompat.getColor(context, R.color.textColorMaxContrast))
- if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
- viewThemeUtils.platform.colorViewBackground(binding.messageQuote.quoteColoredView, ColorRole.PRIMARY)
- } else {
- binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
+ 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
+ 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,
+ true,
+ viewThemeUtils
+ )
+
+ binding.messageQuote.quotedMessageAuthor
+ .setTextColor(ContextCompat.getColor(context, R.color.textColorMaxContrast))
+
+ if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
+ viewThemeUtils.platform.colorViewBackground(
+ binding.messageQuote.quoteColoredView,
+ ColorRole.PRIMARY
+ )
+ } else {
+ binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
+ }
+ binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
+ } catch (e: Exception) {
+ Log.d(TAG, "Error when processing parent message in view holder", e)
+ }
}
-
- binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
} else {
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java
index c73826c4ff..fb1f6f85d4 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingPreviewMessageViewHolder.java
@@ -18,7 +18,7 @@
import com.nextcloud.talk.R;
import com.nextcloud.talk.databinding.ItemCustomIncomingPreviewMessageBinding;
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
-import com.nextcloud.talk.models.json.chat.ChatMessage;
+import com.nextcloud.talk.chat.data.model.ChatMessage;
import com.nextcloud.talk.utils.TextMatchers;
import java.util.HashMap;
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 52e4f2bbcd..e44901a818 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
@@ -11,9 +11,11 @@ package com.nextcloud.talk.adapters.messages
import android.content.Context
import android.text.TextUtils
+import android.util.Log
import android.util.TypedValue
import android.view.View
import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector
import coil.load
import com.nextcloud.android.common.ui.theme.utils.ColorRole
@@ -25,7 +27,7 @@ import com.nextcloud.talk.databinding.ItemCustomIncomingTextMessageBinding
import com.nextcloud.talk.extensions.loadBotsAvatar
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DateUtils
@@ -33,6 +35,13 @@ import com.nextcloud.talk.utils.TextMatchers
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
@@ -99,14 +108,14 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
if (message.lastEditTimestamp != 0L && !message.isDeleted) {
binding.messageEditIndicator.visibility = View.VISIBLE
- binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp)
+ binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp!!)
} else {
binding.messageEditIndicator.visibility = View.GONE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
}
// parent message handling
- if (!message.isDeleted && message.parentMessage != null) {
+ if (!message.isDeleted && message.parentMessageId != null) {
processParentMessage(message)
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
} else {
@@ -176,44 +185,73 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
}
private fun processParentMessage(message: ChatMessage) {
- val parentChatMessage = message.parentMessage
- parentChatMessage!!.activeUser = message.activeUser
- parentChatMessage.imageUrl?.let {
- binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
- binding.messageQuote.quotedMessageImage.load(it) {
- addHeader(
- "Authorization",
- ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
- )
+ if (message.parentMessageId != null && !message.isDeleted) {
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ val chatActivity = commonMessageInterface 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
+ 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 =
+ if (parentChatMessage.actorDisplayName.isNullOrEmpty()) {
+ context.getText(R.string.nc_nick_guest)
+ } else {
+ parentChatMessage.actorDisplayName
+ }
+
+ binding.messageQuote.quotedMessage.text = messageUtils
+ .enrichChatReplyMessageText(
+ binding.messageQuote.quotedMessage.context,
+ parentChatMessage,
+ true,
+ viewThemeUtils
+ )
+
+ if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
+ viewThemeUtils.platform.colorViewBackground(
+ binding.messageQuote.quoteColoredView,
+ ColorRole.PRIMARY
+ )
+ } else {
+ binding.messageQuote.quoteColoredView.setBackgroundColor(
+ ContextCompat.getColor(
+ binding.messageQuote.quoteColoredView.context,
+ R.color.high_emphasis_text
+ )
+ )
+ }
+
+ binding.messageQuote.quotedChatMessageView.setOnClickListener {
+ val chatActivity = commonMessageInterface as ChatActivity
+ chatActivity.jumpToQuotedMessage(parentChatMessage)
+ }
+ } catch (e: Exception) {
+ Log.d(TAG, "Error when processing parent message in view holder", e)
+ }
}
- } ?: run {
- binding.messageQuote.quotedMessageImage.visibility = View.GONE
- }
- binding.messageQuote.quotedMessageAuthor.text = if (parentChatMessage.actorDisplayName.isNullOrEmpty()) {
- context.getText(R.string.nc_nick_guest)
- } else {
- parentChatMessage.actorDisplayName
- }
-
- binding.messageQuote.quotedMessage.text = messageUtils
- .enrichChatReplyMessageText(
- binding.messageQuote.quotedMessage.context,
- parentChatMessage,
- true,
- viewThemeUtils
- )
-
- if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
- viewThemeUtils.platform.colorViewBackground(binding.messageQuote.quoteColoredView, ColorRole.PRIMARY)
- } else {
- binding.messageQuote.quoteColoredView.setBackgroundColor(
- ContextCompat.getColor(binding.messageQuote.quoteColoredView.context, R.color.high_emphasis_text)
- )
- }
-
- binding.messageQuote.quotedChatMessageView.setOnClickListener {
- val chatActivity = commonMessageInterface as ChatActivity
- chatActivity.jumpToQuotedMessage(parentChatMessage)
}
}
@@ -234,5 +272,6 @@ class IncomingTextMessageViewHolder(itemView: View, payload: Any) :
companion object {
const val TEXT_SIZE_MULTIPLIER = 2.5
+ private val TAG = IncomingTextMessageViewHolder::class.java.simpleName
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt
index 8c2f321b0d..0ba9273706 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/IncomingVoiceMessageViewHolder.kt
@@ -24,17 +24,23 @@ import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R
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.databinding.ItemCustomIncomingVoiceMessageBinding
import com.nextcloud.talk.extensions.loadBotsAvatar
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
-import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.util.concurrent.ExecutionException
import javax.inject.Inject
@@ -203,14 +209,17 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
Log.d(TAG, "WorkInfo.State.RUNNING in ViewHolder")
showVoiceMessageLoading()
}
+
WorkInfo.State.SUCCEEDED -> {
Log.d(TAG, "WorkInfo.State.SUCCEEDED in ViewHolder")
showPlayButton()
}
+
WorkInfo.State.FAILED -> {
Log.d(TAG, "WorkInfo.State.FAILED in ViewHolder")
showPlayButton()
}
+
else -> {
}
}
@@ -269,40 +278,62 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
}
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
- if (!message.isDeleted && message.parentMessage != null) {
- val parentChatMessage = message.parentMessage
- parentChatMessage!!.activeUser = message.activeUser
- parentChatMessage.imageUrl?.let {
- binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
- binding.messageQuote.quotedMessageImage.load(it) {
- addHeader(
- "Authorization",
- ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
+ if (message.parentMessageId != null && !message.isDeleted) {
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ val chatActivity = commonMessageInterface 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
+ 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,
+ true,
+ viewThemeUtils
+ )
+
+ binding.messageQuote.quotedMessageAuthor
+ .setTextColor(ContextCompat.getColor(context!!, R.color.textColorMaxContrast))
+
+ if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
+ viewThemeUtils.platform.colorViewBackground(
+ binding.messageQuote.quoteColoredView,
+ ColorRole.PRIMARY
+ )
+ } else {
+ binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
+ }
+
+ binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
+ } catch (e: Exception) {
+ Log.d(TAG, "Error when processing parent message in view holder", e)
}
- } ?: 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,
- true,
- viewThemeUtils
- )
-
- binding.messageQuote.quotedMessageAuthor
- .setTextColor(ContextCompat.getColor(context!!, R.color.textColorMaxContrast))
-
- if (parentChatMessage.actorId?.equals(message.activeUser!!.userId) == true) {
- viewThemeUtils.platform.colorViewBackground(binding.messageQuote.quoteColoredView, ColorRole.PRIMARY)
- } else {
- binding.messageQuote.quoteColoredView.setBackgroundResource(R.color.textColorMaxContrast)
}
-
- binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
} else {
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt
index 4c9f729279..b9e7912197 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/LinkPreview.kt
@@ -14,7 +14,7 @@ import android.view.View
import coil.load
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.databinding.ReferenceInsideMessageBinding
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.models.json.opengraph.OpenGraphOverall
import com.nextcloud.talk.utils.ApiUtils
import io.reactivex.Observer
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt
index 567f3936c9..94386c11af 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLinkPreviewMessageViewHolder.kt
@@ -9,17 +9,21 @@ package com.nextcloud.talk.adapters.messages
import android.annotation.SuppressLint
import android.content.Context
+import android.util.Log
import android.view.View
import androidx.appcompat.content.res.AppCompatResources
+import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector
import coil.load
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R
+import com.nextcloud.talk.adapters.messages.OutcomingPollMessageViewHolder.Companion
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.databinding.ItemCustomOutcomingLinkPreviewMessageBinding
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
@@ -27,6 +31,11 @@ import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
+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)
@@ -138,34 +147,53 @@ class OutcomingLinkPreviewMessageViewHolder(outcomingView: View, payload: Any) :
}
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
- if (!message.isDeleted && message.parentMessage != null) {
- val parentChatMessage = message.parentMessage
- parentChatMessage!!.activeUser = message.activeUser
- parentChatMessage.imageUrl?.let {
- binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
- binding.messageQuote.quotedMessageImage.load(it) {
- addHeader(
- "Authorization",
- ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
+ if (message.parentMessageId != null && !message.isDeleted) {
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ val chatActivity = commonMessageInterface 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
+ 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.visibility = View.VISIBLE
+ } catch (e: Exception) {
+ Log.d(TAG, "Error when processing parent message in view holder", e)
}
- } ?: 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.visibility = View.VISIBLE
} else {
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt
index 2a50ecb020..f9208eb2e8 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingLocationMessageViewHolder.kt
@@ -18,16 +18,19 @@ import android.view.View
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.content.res.AppCompatResources
+import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector
import coil.load
import com.google.android.flexbox.FlexboxLayout
import com.google.android.material.snackbar.Snackbar
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R
+import com.nextcloud.talk.adapters.messages.IncomingPollMessageViewHolder.Companion
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.databinding.ItemCustomOutcomingLocationMessageBinding
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
@@ -35,6 +38,11 @@ import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.UriUtils
import com.nextcloud.talk.utils.message.MessageUtils
import com.stfalcon.chatkit.messages.MessageHolders
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.net.URLEncoder
import javax.inject.Inject
import kotlin.math.roundToInt
@@ -190,34 +198,53 @@ class OutcomingLocationMessageViewHolder(incomingView: View) :
}
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
- if (!message.isDeleted && message.parentMessage != null) {
- val parentChatMessage = message.parentMessage
- parentChatMessage!!.activeUser = message.activeUser
- parentChatMessage.imageUrl?.let {
- binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
- binding.messageQuote.quotedMessageImage.load(it) {
- addHeader(
- "Authorization",
- ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
+ if (message.parentMessageId != null && !message.isDeleted) {
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ val chatActivity = commonMessageInterface 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
+ 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.visibility = View.VISIBLE
+ } catch (e: Exception) {
+ Log.d(TAG, "Error when processing parent message in view holder", e)
}
- } ?: 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.visibility = View.VISIBLE
} else {
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt
index 72fe4d986c..18473daeab 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPollMessageViewHolder.kt
@@ -9,18 +9,21 @@ package com.nextcloud.talk.adapters.messages
import android.annotation.SuppressLint
import android.content.Context
+import android.util.Log
import android.view.View
import androidx.appcompat.content.res.AppCompatResources
+import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector
import coil.load
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R
+import com.nextcloud.talk.adapters.messages.IncomingPollMessageViewHolder.Companion
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.databinding.ItemCustomOutcomingPollMessageBinding
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.polls.ui.PollMainDialogFragment
import com.nextcloud.talk.ui.theme.ViewThemeUtils
@@ -29,6 +32,11 @@ import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
+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)
@@ -153,34 +161,53 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) :
}
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
- if (!message.isDeleted && message.parentMessage != null) {
- val parentChatMessage = message.parentMessage
- parentChatMessage!!.activeUser = message.activeUser
- parentChatMessage.imageUrl?.let {
- binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
- binding.messageQuote.quotedMessageImage.load(it) {
- addHeader(
- "Authorization",
- ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
+ if (message.parentMessageId != null && !message.isDeleted) {
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ val chatActivity = commonMessageInterface 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
+ 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.visibility = View.VISIBLE
+ } catch (e: Exception) {
+ Log.d(TAG, "Error when processing parent message in view holder", e)
}
- } ?: 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.visibility = View.VISIBLE
} else {
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java
index 81f80a582a..21509d5020 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingPreviewMessageViewHolder.java
@@ -17,7 +17,7 @@
import com.nextcloud.talk.R;
import com.nextcloud.talk.databinding.ItemCustomOutcomingPreviewMessageBinding;
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
-import com.nextcloud.talk.models.json.chat.ChatMessage;
+import com.nextcloud.talk.chat.data.model.ChatMessage;
import com.nextcloud.talk.utils.TextMatchers;
import java.util.HashMap;
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 eaffe1312a..ba0b4fe1ae 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
@@ -9,6 +9,7 @@
package com.nextcloud.talk.adapters.messages
import android.content.Context
+import android.util.Log
import android.util.TypedValue
import android.view.View
import androidx.core.content.res.ResourcesCompat
@@ -20,8 +21,8 @@ import com.nextcloud.talk.R
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.databinding.ItemCustomOutcomingTextMessageBinding
-import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
@@ -29,6 +30,11 @@ import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.TextMatchers
import com.nextcloud.talk.utils.message.MessageUtils
import com.stfalcon.chatkit.messages.MessageHolders.OutcomingTextMessageViewHolder
+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)
@@ -91,14 +97,14 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH
if (message.lastEditTimestamp != 0L && !message.isDeleted) {
binding.messageEditIndicator.visibility = View.VISIBLE
- binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp)
+ binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.lastEditTimestamp!!)
} else {
binding.messageEditIndicator.visibility = View.GONE
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
}
// parent message handling
- if (!message.isDeleted && message.parentMessage != null) {
+ if (!message.isDeleted && message.parentMessageId != null) {
processParentMessage(message)
binding.messageQuote.quotedChatMessageView.visibility = View.VISIBLE
} else {
@@ -148,36 +154,58 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH
}
private fun processParentMessage(message: ChatMessage) {
- val parentChatMessage = message.parentMessage
- parentChatMessage!!.activeUser = message.activeUser
- parentChatMessage.imageUrl?.let {
- binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
- binding.messageQuote.quotedMessageImage.load(it) {
- addHeader(
- "Authorization",
- ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
- )
+ if (message.parentMessageId != null && !message.isDeleted) {
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ val chatActivity = commonMessageInterface 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
+ 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 = commonMessageInterface as ChatActivity
+ chatActivity.jumpToQuotedMessage(parentChatMessage)
+ }
+ } catch (e: Exception) {
+ Log.d(TAG, "Error when processing parent message in view holder", e)
+ }
}
- } ?: 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 = commonMessageInterface as ChatActivity
- chatActivity.jumpToQuotedMessage(parentChatMessage)
}
}
@@ -191,5 +219,6 @@ class OutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewH
companion object {
const val TEXT_SIZE_MULTIPLIER = 2.5
+ private val TAG = OutcomingTextMessageViewHolder::class.java.simpleName
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt
index ccd32b0ef4..474999af03 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/OutcomingVoiceMessageViewHolder.kt
@@ -17,16 +17,19 @@ import android.view.View
import android.widget.SeekBar
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
import androidx.work.WorkInfo
import androidx.work.WorkManager
import autodagger.AutoInjector
import coil.load
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.talk.R
+import com.nextcloud.talk.adapters.messages.IncomingPollMessageViewHolder.Companion
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.databinding.ItemCustomOutcomingVoiceMessageBinding
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
@@ -34,6 +37,11 @@ import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.message.MessageUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
import java.util.concurrent.ExecutionException
import javax.inject.Inject
@@ -238,14 +246,17 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
Log.d(TAG, "WorkInfo.State.RUNNING in ViewHolder")
showVoiceMessageLoading()
}
+
WorkInfo.State.SUCCEEDED -> {
Log.d(TAG, "WorkInfo.State.SUCCEEDED in ViewHolder")
showPlayButton()
}
+
WorkInfo.State.FAILED -> {
Log.d(TAG, "WorkInfo.State.FAILED in ViewHolder")
showPlayButton()
}
+
else -> {
Log.d(TAG, "WorkInfo.State unused in ViewHolder")
}
@@ -264,34 +275,53 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
}
private fun setParentMessageDataOnMessageItem(message: ChatMessage) {
- if (!message.isDeleted && message.parentMessage != null) {
- val parentChatMessage = message.parentMessage
- parentChatMessage!!.activeUser = message.activeUser
- parentChatMessage.imageUrl?.let {
- binding.messageQuote.quotedMessageImage.visibility = View.VISIBLE
- binding.messageQuote.quotedMessageImage.load(it) {
- addHeader(
- "Authorization",
- ApiUtils.getCredentials(message.activeUser!!.username, message.activeUser!!.token)!!
+ if (message.parentMessageId != null && !message.isDeleted) {
+ CoroutineScope(Dispatchers.Main).launch {
+ try {
+ val chatActivity = commonMessageInterface 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
+ 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.visibility = View.VISIBLE
+ } catch (e: Exception) {
+ Log.d(TAG, "Error when processing parent message in view holder", e)
}
- } ?: 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.visibility = View.VISIBLE
} else {
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageInterface.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageInterface.kt
index 4833a50b08..c7358b9be5 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageInterface.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageInterface.kt
@@ -6,7 +6,7 @@
*/
package com.nextcloud.talk.adapters.messages
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
interface PreviewMessageInterface {
fun onPreviewMessageLongClick(chatMessage: ChatMessage)
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt
index 7ff68e9519..02159ceabe 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/PreviewMessageViewHolder.kt
@@ -34,7 +34,7 @@ import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
import com.nextcloud.talk.extensions.loadFederatedUserAvatar
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.DateUtils
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/Reaction.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/Reaction.kt
index 4d00f445dd..801c2220b5 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/Reaction.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/Reaction.kt
@@ -12,7 +12,7 @@ import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.vanniktech.emoji.EmojiTextView
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageInterface.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageInterface.kt
index 8258921d8a..18199b7596 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageInterface.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageInterface.kt
@@ -6,7 +6,7 @@
*/
package com.nextcloud.talk.adapters.messages
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
interface SystemMessageInterface {
fun expandSystemMessage(chatMessage: ChatMessage)
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageViewHolder.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageViewHolder.kt
index e442e2a4fe..a9332636aa 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageViewHolder.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/SystemMessageViewHolder.kt
@@ -19,7 +19,7 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.databinding.ItemSystemMessageBinding
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.utils.DateUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
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 f5121a6e55..b9185c7aab 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
@@ -33,7 +33,7 @@ public List getItems() {
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
- super.onBindViewHolder(holder, position);
+
if (holder instanceof IncomingTextMessageViewHolder) {
((IncomingTextMessageViewHolder) holder).assignCommonMessageInterface(chatActivity);
@@ -66,5 +66,7 @@ public void onBindViewHolder(ViewHolder holder, int position) {
} else if (holder instanceof CallStartedViewHolder) {
((CallStartedViewHolder) holder).assignCallStartedMessageInterface(chatActivity);
}
+
+ super.onBindViewHolder(holder, position);
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/UnreadNoticeMessageViewHolder.java b/app/src/main/java/com/nextcloud/talk/adapters/messages/UnreadNoticeMessageViewHolder.java
index 888b62c94c..6615b4f0d9 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/UnreadNoticeMessageViewHolder.java
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/UnreadNoticeMessageViewHolder.java
@@ -9,7 +9,7 @@
import android.view.View;
-import com.nextcloud.talk.models.json.chat.ChatMessage;
+import com.nextcloud.talk.chat.data.model.ChatMessage;
import com.stfalcon.chatkit.messages.MessageHolders;
public class UnreadNoticeMessageViewHolder extends MessageHolders.SystemMessageViewHolder {
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/messages/VoiceMessageInterface.kt b/app/src/main/java/com/nextcloud/talk/adapters/messages/VoiceMessageInterface.kt
index df04577525..aada2ade23 100644
--- a/app/src/main/java/com/nextcloud/talk/adapters/messages/VoiceMessageInterface.kt
+++ b/app/src/main/java/com/nextcloud/talk/adapters/messages/VoiceMessageInterface.kt
@@ -6,7 +6,7 @@
*/
package com.nextcloud.talk.adapters.messages
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
interface VoiceMessageInterface {
fun updateMediaPlayerProgressBySlider(message: ChatMessage, progress: Int)
diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt
index 95a6ce2544..261e869f69 100644
--- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt
+++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt
@@ -36,6 +36,7 @@ import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.components.filebrowser.webdav.DavUtils
import com.nextcloud.talk.dagger.modules.BusModule
import com.nextcloud.talk.dagger.modules.ContextModule
+import com.nextcloud.talk.dagger.modules.DaosModule
import com.nextcloud.talk.dagger.modules.DatabaseModule
import com.nextcloud.talk.dagger.modules.ManagerModule
import com.nextcloud.talk.dagger.modules.RepositoryModule
@@ -79,7 +80,8 @@ import javax.inject.Singleton
RepositoryModule::class,
UtilsModule::class,
ThemeModule::class,
- ManagerModule::class
+ ManagerModule::class,
+ DaosModule::class
]
)
@Singleton
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 0eada3e669..987d369582 100644
--- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
@@ -59,6 +59,7 @@ import androidx.emoji2.text.EmojiCompat
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.commit
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -104,6 +105,7 @@ import com.nextcloud.talk.adapters.messages.UnreadNoticeMessageViewHolder
import com.nextcloud.talk.adapters.messages.VoiceMessageInterface
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel
import com.nextcloud.talk.conversationinfo.ConversationInfoActivity
@@ -119,14 +121,9 @@ import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
import com.nextcloud.talk.location.LocationPickerActivity
import com.nextcloud.talk.messagesearch.MessageSearchActivity
import com.nextcloud.talk.models.domain.ConversationModel
-import com.nextcloud.talk.models.domain.ConversationReadOnlyState
-import com.nextcloud.talk.models.domain.ConversationType
-import com.nextcloud.talk.models.domain.LobbyState
-import com.nextcloud.talk.models.domain.ObjectType
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
-import com.nextcloud.talk.models.json.chat.ChatMessage
-import com.nextcloud.talk.models.json.chat.ChatOverall
import com.nextcloud.talk.models.json.chat.ReadStatus
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.polls.ui.PollCreateDialogFragment
import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity
import com.nextcloud.talk.shareditems.activities.SharedItemsActivity
@@ -183,6 +180,8 @@ import com.stfalcon.chatkit.messages.MessagesListAdapter
import com.stfalcon.chatkit.utils.DateFormatter
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.Subscribe
@@ -285,8 +284,6 @@ class ChatActivity :
private var roomPassword: String = ""
var credentials: String? = null
var currentConversation: ConversationModel? = null
- private var globalLastKnownFutureMessageId = -1
- private var globalLastKnownPastMessageId = -1
var adapter: TalkMessagesListAdapter? = null
var mentionAutocomplete: Autocomplete<*>? = null
var layoutManager: LinearLayoutManager? = null
@@ -294,9 +291,8 @@ class ChatActivity :
var newMessagesCount = 0
var startCallFromNotification: Boolean = false
var startCallFromRoomSwitch: Boolean = false
- lateinit var roomId: String
+ // lateinit var roomId: String
var voiceOnly: Boolean = true
- var isFirstMessagesProcessing = true
private lateinit var path: String
var myFirstMessage: CharSequence? = null
@@ -408,6 +404,7 @@ class ChatActivity :
handleIntent(intent)
chatViewModel = ViewModelProvider(this, viewModelFactory)[ChatViewModel::class.java]
+
messageInputViewModel = ViewModelProvider(this, viewModelFactory)[MessageInputViewModel::class.java]
binding.progressBar.visibility = View.VISIBLE
@@ -463,7 +460,7 @@ class ChatActivity :
private fun handleIntent(intent: Intent) {
val extras: Bundle? = intent.extras
- roomId = extras?.getString(KEY_ROOM_ID).orEmpty()
+ // roomId = extras?.getString(KEY_ROOM_ID).orEmpty()
roomToken = extras?.getString(KEY_ROOM_TOKEN).orEmpty()
sharedText = extras?.getString(BundleKeys.KEY_SHARED_TEXT).orEmpty()
@@ -521,12 +518,37 @@ class ChatActivity :
@Suppress("LongMethod")
private fun initObservers() {
Log.d(TAG, "initObservers Called")
+
+ this.lifecycleScope.launch {
+ chatViewModel.getConversationFlow
+ .onEach { conversationModel ->
+ currentConversation = conversationModel
+
+ val urlForChatting = ApiUtils.getUrlForChat(chatApiVersion, conversationUser?.baseUrl, roomToken)
+ val credentials = ApiUtils.getCredentials(conversationUser!!.username, conversationUser!!.token)
+
+ chatViewModel.setData(
+ currentConversation!!,
+ credentials!!,
+ urlForChatting
+ )
+
+ logConversationInfos("GetRoomSuccessState")
+
+ if (adapter == null) {
+ initAdapter()
+ binding.messagesListView.setAdapter(adapter)
+ layoutManager = binding.messagesListView.layoutManager as LinearLayoutManager?
+ }
+
+ chatViewModel.getCapabilities(conversationUser!!, roomToken, currentConversation!!)
+ }.collect()
+ }
+
chatViewModel.getRoomViewState.observe(this) { state ->
when (state) {
is ChatViewModel.GetRoomSuccessState -> {
- currentConversation = state.conversationModel
- logConversationInfos("GetRoomSuccessState")
- chatViewModel.getCapabilities(conversationUser!!, roomToken, currentConversation!!)
+ // unused atm
}
is ChatViewModel.GetRoomErrorState -> {
@@ -569,23 +591,16 @@ class ChatActivity :
binding.chatToolbar.setOnClickListener { _ -> showConversationInfoScreen() }
}
- if (adapter == null) {
- initAdapter()
- binding.messagesListView.setAdapter(adapter)
- layoutManager = binding.messagesListView.layoutManager as LinearLayoutManager?
- }
-
loadAvatarForStatusBar()
setupSwipeToReply()
setActionBarTitle()
updateRoomTimerHandler()
- chatViewModel.refreshChatParams(
- setupFieldsForPullChatMessages(
- false,
- 0,
- false
- )
+ val urlForChatting = ApiUtils.getUrlForChat(chatApiVersion, conversationUser?.baseUrl, roomToken)
+
+ chatViewModel.loadMessages(
+ withCredentials = credentials!!,
+ withUrl = urlForChatting,
)
}
@@ -604,7 +619,7 @@ class ChatActivity :
sessionIdAfterRoomJoined = currentConversation!!.sessionId
ApplicationWideCurrentRoomHolder.getInstance().session = currentConversation!!.sessionId
- ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = currentConversation!!.roomId
+ // ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = currentConversation!!.roomId
ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken = currentConversation!!.token
ApplicationWideCurrentRoomHolder.getInstance().userInRoom = conversationUser
@@ -627,9 +642,7 @@ class ChatActivity :
}
}
- is ChatViewModel.JoinRoomErrorState -> {
- Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
- }
+ is ChatViewModel.JoinRoomErrorState -> {}
else -> {}
}
@@ -670,8 +683,8 @@ class ChatActivity :
is MessageInputViewModel.SendChatMessageSuccessState -> {
myFirstMessage = state.message
- if (binding.popupBubbleView.isShown == true) {
- binding.popupBubbleView.hide()
+ if (binding.unreadMessagesPopup.isShown == true) {
+ binding.unreadMessagesPopup.hide()
}
binding.messagesListView.smoothScrollToPosition(0)
}
@@ -682,8 +695,8 @@ class ChatActivity :
if (code.toString().startsWith("2")) {
myFirstMessage = state.message
- if (binding.popupBubbleView.isShown == true) {
- binding.popupBubbleView.hide()
+ if (binding.unreadMessagesPopup.isShown == true) {
+ binding.unreadMessagesPopup.hide()
}
binding.messagesListView.smoothScrollToPosition(0)
@@ -705,6 +718,11 @@ class ChatActivity :
Snackbar.LENGTH_LONG
).show()
}
+
+ val id = state.msg.ocs!!.data!!.parentMessage!!.id.toString()
+ val index = adapter?.getMessagePositionById(id) ?: 0
+ val message = adapter?.items?.get(index)?.item as ChatMessage
+ setMessageAsDeleted(message)
}
is ChatViewModel.DeleteChatMessageErrorState -> {
@@ -720,7 +738,7 @@ 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)
+ // bundle.putString(KEY_ROOM_ID, state.roomOverall.ocs!!.data!!.roomId)
leaveRoom {
val chatIntent = Intent(context, ChatActivity::class.java)
@@ -738,128 +756,77 @@ class ChatActivity :
}
}
- chatViewModel.getFieldMapForChat.observe(this) { fieldMap ->
- if (fieldMap.isNotEmpty()) {
- chatViewModel.pullChatMessages(
- credentials!!,
- ApiUtils.getUrlForChat(chatApiVersion, conversationUser?.baseUrl, roomToken)
- )
- }
- }
-
- chatViewModel.pullChatMessageViewState.observe(this) { state ->
+ chatViewModel.chatMessageViewState.observe(this) { state ->
when (state) {
- is ChatViewModel.PullChatMessageSuccessState -> {
- Log.d(TAG, "PullChatMessageSuccess: Code: ${state.response.code()}")
- when (state.response.code()) {
- HTTP_CODE_OK -> {
- Log.d(TAG, "lookIntoFuture: ${state.lookIntoFuture}")
- val chatOverall = state.response.body() as ChatOverall?
- var chatMessageList = chatOverall?.ocs!!.data!!
-
- val newXChatLastCommonRead = state.response.headers()["X-Chat-Last-Common-Read"]?.let {
- Integer.parseInt(it)
- }
-
- processHeaderChatLastGiven(state.response, state.lookIntoFuture)
-
- chatMessageList = handleSystemMessages(chatMessageList)
-
- if (chatMessageList.isEmpty()) {
- chatViewModel.refreshChatParams(
- setupFieldsForPullChatMessages(
- true,
- newXChatLastCommonRead,
- true
- )
- )
- return@observe
- }
-
- determinePreviousMessageIds(chatMessageList)
-
- handleExpandableSystemMessages(chatMessageList)
+ is ChatViewModel.ChatMessageStartState -> {
+ // Handle UI on first load
+ cancelNotificationsForCurrentConversation()
+ binding.progressBar.visibility = View.GONE
+ binding.messagesListView.visibility = View.VISIBLE
+ collapseSystemMessages()
+ }
- if (chatMessageList.isNotEmpty() &&
- ChatMessage.SystemMessageType.CLEARED_CHAT == chatMessageList[0].systemMessageType
- ) {
- adapter?.clear()
- adapter?.notifyDataSetChanged()
- }
+ is ChatViewModel.ChatMessageUpdateState -> {
+ // unused atm
+ }
- var lastAdapterId = getLastAdapterId()
- val oneNewMessage = (lastAdapterId != 0 || chatMessageList.size == 1)
-
- if (
- state.lookIntoFuture &&
- oneNewMessage &&
- chatMessageList[0].jsonMessageId > lastAdapterId
- ) {
- processMessagesFromTheFuture(chatMessageList)
- } else if (!state.lookIntoFuture) {
- processMessagesNotFromTheFuture(chatMessageList)
- collapseSystemMessages()
- }
+ is ChatViewModel.ChatMessageErrorState -> {
+ // unused atm
+ }
- updateReadStatusOfAllMessages(newXChatLastCommonRead)
+ else -> {}
+ }
+ }
- processCallStartedMessages(chatMessageList)
+ this.lifecycleScope.launch {
+ chatViewModel.getMessageFlow
+ .onEach { pair ->
+ val lookIntoFuture = pair.first
+ var chatMessageList = pair.second
- adapter?.notifyDataSetChanged()
+ chatMessageList = handleSystemMessages(chatMessageList)
- chatViewModel.refreshChatParams(
- setupFieldsForPullChatMessages(
- true,
- newXChatLastCommonRead,
- true
- )
- )
- }
+ determinePreviousMessageIds(chatMessageList)
- HTTP_CODE_NOT_MODIFIED -> {
- chatViewModel.refreshChatParams(
- setupFieldsForPullChatMessages(
- true,
- globalLastKnownPastMessageId,
- true
- )
- )
- }
+ handleExpandableSystemMessages(chatMessageList)
- HTTP_CODE_PRECONDITION_FAILED -> {
- chatViewModel.refreshChatParams(
- setupFieldsForPullChatMessages(
- true,
- globalLastKnownPastMessageId,
- true
- )
- )
- }
+ if (chatMessageList.isNotEmpty() &&
+ ChatMessage.SystemMessageType.CLEARED_CHAT == chatMessageList[0].systemMessageType
+ ) {
+ adapter?.clear()
+ adapter?.notifyDataSetChanged()
+ }
- else -> {}
+ if (lookIntoFuture) {
+ processMessagesFromTheFuture(chatMessageList)
+ } else {
+ processMessagesNotFromTheFuture(chatMessageList)
+ collapseSystemMessages()
}
processExpiredMessages()
- if (isFirstMessagesProcessing) {
- cancelNotificationsForCurrentConversation()
- isFirstMessagesProcessing = false
- binding.progressBar.visibility = View.GONE
- binding.messagesListView.visibility = View.VISIBLE
+ processCallStartedMessages(chatMessageList)
- collapseSystemMessages()
- }
+ adapter?.notifyDataSetChanged()
}
+ .collect()
+ }
- is ChatViewModel.PullChatMessageCompleteState -> {
- Log.d(TAG, "PullChatMessageCompleted")
+ this.lifecycleScope.launch {
+ chatViewModel.getUpdateMessageFlow
+ .onEach {
+ updateMessageInsideAdapter(it)
}
+ .collect()
+ }
- is ChatViewModel.PullChatMessageErrorState -> {
- Log.d(TAG, "PullChatMessageError")
+ this.lifecycleScope.launch {
+ chatViewModel.getLastCommonReadFlow
+ .onEach {
+ updateReadStatusOfAllMessages(it)
+ processExpiredMessages()
}
-
- else -> {}
- }
+ .collect()
}
chatViewModel.reactionDeletedViewState.observe(this) { state ->
@@ -916,6 +883,11 @@ class ChatActivity :
).show()
}
}
+ val newString = state.messageEdited.ocs?.data?.parentMessage?.message ?: "(null)"
+ val id = state.messageEdited.ocs?.data?.parentMessage?.id.toString()
+ val index = adapter?.getMessagePositionById(id) ?: 0
+ val message = adapter?.items?.get(index)?.item as ChatMessage
+ setMessageAsEdited(message, newString)
}
is MessageInputViewModel.EditMessageErrorState -> {
@@ -978,9 +950,9 @@ class ChatActivity :
setupSwipeToReply()
- binding.popupBubbleView.setRecyclerView(binding.messagesListView)
+ binding.unreadMessagesPopup.setRecyclerView(binding.messagesListView)
- binding.popupBubbleView.setPopupBubbleListener { _ ->
+ binding.unreadMessagesPopup.setPopupBubbleListener { _ ->
if (newMessagesCount != 0) {
val scrollPosition = if (newMessagesCount - 1 < 0) {
0
@@ -1005,14 +977,15 @@ class ChatActivity :
binding.let { viewThemeUtils.material.themeFAB(it.voiceRecordingLock) }
- binding.let { viewThemeUtils.material.colorMaterialButtonPrimaryFilled(it.popupBubbleView) }
+ binding.let { viewThemeUtils.material.colorMaterialButtonPrimaryFilled(it.unreadMessagesPopup) }
binding.messagesListView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
- if (layoutManager!!.findFirstCompletelyVisibleItemPosition() > 0) {
+ if (layoutManager!!.findFirstCompletelyVisibleItemPosition() > 0 &&
+ !binding.unreadMessagesPopup.isShown) {
binding.scrollDownButton.visibility = View.VISIBLE
} else {
binding.scrollDownButton.visibility = View.GONE
@@ -1022,8 +995,8 @@ class ChatActivity :
if (layoutManager!!.findFirstCompletelyVisibleItemPosition() < newMessagesCount) {
newMessagesCount = 0
- if (binding.popupBubbleView.isShown == true) {
- binding.popupBubbleView.hide()
+ if (binding.unreadMessagesPopup.isShown) {
+ binding.unreadMessagesPopup.hide()
}
}
}
@@ -1303,7 +1276,7 @@ class ChatActivity :
.setInterpolator(AccelerateDecelerateInterpolator())
.duration = TYPING_INDICATOR_ANIMATION_DURATION
} else {
- binding.typingIndicatorWrapper.visibility = View.GONE
+ binding.typingIndicatorWrapper.visibility = View.INVISIBLE
binding.typingIndicatorWrapper.y += DisplayUtils.convertDpToPixel(18f, context)
}
}
@@ -1412,15 +1385,15 @@ class ChatActivity :
fun isOneToOneConversation() =
currentConversation != null && currentConversation?.type != null &&
- currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+ currentConversation?.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
private fun isGroupConversation() =
currentConversation != null && currentConversation?.type != null &&
- currentConversation?.type == ConversationType.ROOM_GROUP_CALL
+ currentConversation?.type == ConversationEnums.ConversationType.ROOM_GROUP_CALL
private fun isPublicConversation() =
currentConversation != null && currentConversation?.type != null &&
- currentConversation?.type == ConversationType.ROOM_PUBLIC_CALL
+ currentConversation?.type == ConversationEnums.ConversationType.ROOM_PUBLIC_CALL
private fun updateRoomTimerHandler() {
val delayForRecursiveCall = if (shouldShowLobby()) {
@@ -1443,7 +1416,7 @@ class ChatActivity :
private fun switchToRoom(token: String, startCallAfterRoomSwitch: Boolean, isVoiceOnlyCall: Boolean) {
if (conversationUser != null) {
runOnUiThread {
- if (currentConversation?.objectType == ObjectType.ROOM) {
+ if (currentConversation?.objectType == ConversationEnums.ObjectType.ROOM) {
Snackbar.make(
binding.root,
context.resources.getString(R.string.switch_to_main_room),
@@ -1826,7 +1799,7 @@ class ChatActivity :
private fun shouldShowLobby(): Boolean {
if (currentConversation != null) {
return CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.WEBINARY_LOBBY) &&
- currentConversation?.lobbyState == LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
+ currentConversation?.lobbyState == ConversationEnums.LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
!ConversationUtils.canModerate(currentConversation!!, spreedCapabilities) &&
!participantPermissions.canIgnoreLobby()
}
@@ -1862,7 +1835,7 @@ class ChatActivity :
private fun isReadOnlyConversation(): Boolean {
return currentConversation?.conversationReadOnlyState != null &&
currentConversation?.conversationReadOnlyState ==
- ConversationReadOnlyState.CONVERSATION_READ_ONLY
+ ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_ONLY
}
private fun checkLobbyState() {
@@ -2099,10 +2072,6 @@ class ChatActivity :
}
}
- private fun hasGrantedPermissions(grantResults: IntArray): Boolean {
- return permissionUtil.isFilesPermissionGranted()
- }
-
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION) {
@@ -2327,7 +2296,7 @@ class ChatActivity :
""
}
- if (currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
+ if (currentConversation?.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
var statusMessage = ""
if (currentConversation?.statusIcon != null) {
statusMessage += currentConversation?.statusIcon
@@ -2337,8 +2306,8 @@ class ChatActivity :
}
statusMessageViewContents(statusMessage)
} else {
- if (currentConversation?.type == ConversationType.ROOM_GROUP_CALL ||
- currentConversation?.type == ConversationType.ROOM_PUBLIC_CALL
+ if (currentConversation?.type == ConversationEnums.ConversationType.ROOM_GROUP_CALL ||
+ currentConversation?.type == ConversationEnums.ConversationType.ROOM_PUBLIC_CALL
) {
var descriptionMessage = ""
descriptionMessage += currentConversation?.description
@@ -2376,12 +2345,12 @@ class ChatActivity :
private fun joinRoomWithPassword() {
// if ApplicationWideCurrentRoomHolder contains a session (because a call is active), then keep the sessionId
- if (ApplicationWideCurrentRoomHolder.getInstance().currentRoomId ==
- currentConversation!!.roomId
+ if (ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken ==
+ currentConversation!!.token
) {
sessionIdAfterRoomJoined = ApplicationWideCurrentRoomHolder.getInstance().session
- ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = roomId
+ // ApplicationWideCurrentRoomHolder.getInstance().currentRoomId = roomId
ApplicationWideCurrentRoomHolder.getInstance().currentRoomToken = roomToken
ApplicationWideCurrentRoomHolder.getInstance().userInRoom = conversationUser
}
@@ -2466,56 +2435,6 @@ class ChatActivity :
}
}
- private fun setupFieldsForPullChatMessages(
- lookIntoFuture: Boolean,
- xChatLastCommonRead: Int?,
- setReadMarker: Boolean
- ): HashMap {
- val fieldMap = HashMap()
- fieldMap["includeLastKnown"] = 0
-
- if (!lookIntoFuture && isFirstMessagesProcessing) {
- if (currentConversation != null) {
- globalLastKnownFutureMessageId = currentConversation!!.lastReadMessage
- globalLastKnownPastMessageId = currentConversation!!.lastReadMessage
- fieldMap["includeLastKnown"] = 1
- }
- }
-
- val lastKnown = if (lookIntoFuture) {
- globalLastKnownFutureMessageId
- } else {
- globalLastKnownPastMessageId
- }
-
- fieldMap["lastKnownMessageId"] = lastKnown
- xChatLastCommonRead?.let {
- fieldMap["lastCommonReadId"] = it
- }
-
- val timeout = if (lookIntoFuture) {
- LOOKING_INTO_FUTURE_TIMEOUT
- } else {
- 0
- }
-
- fieldMap["timeout"] = timeout
- fieldMap["limit"] = MESSAGE_PULL_LIMIT
-
- if (lookIntoFuture) {
- fieldMap["lookIntoFuture"] = 1
- } else {
- fieldMap["lookIntoFuture"] = 0
- }
-
- if (setReadMarker) {
- fieldMap["setReadMarker"] = 1
- } else {
- fieldMap["setReadMarker"] = 0
- }
- return fieldMap
- }
-
private fun processExpiredMessages() {
@SuppressLint("NotifyDataSetChanged")
fun deleteExpiredMessages() {
@@ -2548,6 +2467,7 @@ class ChatActivity :
updateReadStatusOfMessage(message, it)
}
}
+ adapter!!.notifyDataSetChanged()
}
}
@@ -2583,23 +2503,23 @@ class ChatActivity :
unreadChatMessage.timestamp = chatMessageList[0].timestamp
unreadChatMessage.message = context.getString(R.string.nc_new_messages)
adapter?.addToStart(unreadChatMessage, false)
- }
- if (!scrollToEndOnUpdate) {
- binding.popupBubbleView.isShown.let {
- if (it) {
+
+ if (scrollToEndOnUpdate) {
+ binding.scrollDownButton.visibility = View.GONE
+ newMessagesCount = 0
+ } else {
+ if (binding.unreadMessagesPopup.isShown) {
newMessagesCount++
} else {
newMessagesCount = 1
binding.scrollDownButton.visibility = View.GONE
- binding.popupBubbleView.show()
+ binding.unreadMessagesPopup.show()
}
}
- } else {
- binding.scrollDownButton.visibility = View.GONE
- newMessagesCount = 0
}
+
for (chatMessage in chatMessageList) {
chatMessage.activeUser = conversationUser
@@ -2610,9 +2530,9 @@ class ChatActivity :
GROUPED_MESSAGES_SAME_AUTHOR_THRESHOLD > 0
)
chatMessage.isOneToOneConversation =
- (currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL)
+ (currentConversation?.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL)
chatMessage.isFormerOneToOneConversation =
- (currentConversation?.type == ConversationType.FORMER_ONE_TO_ONE)
+ (currentConversation?.type == ConversationEnums.ConversationType.FORMER_ONE_TO_ONE)
it.addToStart(chatMessage, scrollToEndOnUpdate)
}
}
@@ -2640,9 +2560,9 @@ class ChatActivity :
val chatMessage = chatMessageList[i]
chatMessage.isOneToOneConversation =
- currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+ currentConversation?.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
chatMessage.isFormerOneToOneConversation =
- (currentConversation?.type == ConversationType.FORMER_ONE_TO_ONE)
+ (currentConversation?.type == ConversationEnums.ConversationType.FORMER_ONE_TO_ONE)
chatMessage.activeUser = conversationUser
chatMessage.token = roomToken
}
@@ -2687,69 +2607,30 @@ class ChatActivity :
}
}
- private fun processHeaderChatLastGiven(response: Response<*>, isFromTheFuture: Boolean) {
- val xChatLastGivenHeader: String? = response.headers()["X-Chat-Last-Given"]
-
- val header = if (response.headers().size > 0 &&
- xChatLastGivenHeader?.isNotEmpty() == true
- ) {
- xChatLastGivenHeader.toInt()
- } else {
- return
- }
-
- if (header > 0) {
- if (isFromTheFuture) {
- globalLastKnownFutureMessageId = header
- } else {
- if (globalLastKnownFutureMessageId == -1) {
- globalLastKnownFutureMessageId = header
- }
- globalLastKnownPastMessageId = header
- }
- }
- }
-
/**
- * this method must be called after that the adatper has finished loading ChatMessages items
- * it searches by ID the message that was playing,
+ * this method must be called after that the adapter has finished loading ChatMessages items
+ * it searches by ID the message that was playing,s
* then, if it finds it, it restores audio position
* and eventually resumes audio playback
* @author Giacomo Pacini
*/
private fun resumeAudioPlaybackIfNeeded() {
- if (!voiceMessageToRestoreId.equals("")) {
+ if (voiceMessageToRestoreId != "") {
Log.d(RESUME_AUDIO_TAG, "begin method to resume audio playback")
- if (adapter != null) {
- Log.d(RESUME_AUDIO_TAG, "adapter is not null, proceeding")
- val voiceMessagePosition = adapter!!.items!!.indexOfFirst {
- it.item is ChatMessage && (it.item as ChatMessage).id == voiceMessageToRestoreId
- }
- if (voiceMessagePosition >= 0) {
- val currentItem = adapter?.items?.get(voiceMessagePosition)?.item
- if (currentItem is ChatMessage && currentItem.id == voiceMessageToRestoreId) {
- currentlyPlayedVoiceMessage = currentItem
- lastRecordMediaPosition = voiceMessageToRestoreAudioPosition * 1000
- Log.d(RESUME_AUDIO_TAG, "trying to resume audio")
- binding.messagesListView.scrollToPosition(voiceMessagePosition)
- // WORKAROUND TO FETCH FILE INFO:
- currentlyPlayedVoiceMessage!!.getImageUrl()
- // see getImageUrl() source code
- setUpWaveform(currentlyPlayedVoiceMessage!!, voiceMessageToRestoreWasPlaying)
- Log.d(RESUME_AUDIO_TAG, "resume audio procedure completed")
- } else {
- Log.d(RESUME_AUDIO_TAG, "currentItem retrieved was not chatmessage or its id was not correct")
- }
- } else {
- Log.d(
- RESUME_AUDIO_TAG,
- "voiceMessagePosition is -1, adapter # of items: " + adapter!!.itemCount
- )
- }
- } else {
- Log.d(RESUME_AUDIO_TAG, "TalkMessagesListAdapater is null")
- }
+ val pair = getItemFromAdapter(voiceMessageToRestoreId)
+ currentlyPlayedVoiceMessage = pair?.first
+ val voiceMessagePosition = pair?.second!!
+
+ lastRecordMediaPosition = voiceMessageToRestoreAudioPosition * 1000
+ Log.d(RESUME_AUDIO_TAG, "trying to resume audio")
+ binding.messagesListView.scrollToPosition(voiceMessagePosition)
+ // WORKAROUND TO FETCH FILE INFO:
+ currentlyPlayedVoiceMessage!!.getImageUrl()
+ // see getImageUrl() source code
+ setUpWaveform(currentlyPlayedVoiceMessage!!, voiceMessageToRestoreWasPlaying)
+ Log.d(RESUME_AUDIO_TAG, "resume audio procedure completed")
+
} else {
Log.d(RESUME_AUDIO_TAG, "No voice message to restore")
}
@@ -2758,6 +2639,29 @@ class ChatActivity :
voiceMessageToRestoreWasPlaying = false
}
+ private fun getItemFromAdapter(messageId: String): Pair? {
+ if (adapter != null) {
+ val messagePosition = adapter!!.items!!.indexOfFirst {
+ it.item is ChatMessage && (it.item as ChatMessage).id == messageId
+ }
+ if (messagePosition >= 0) {
+ val currentItem = adapter?.items?.get(messagePosition)?.item
+ if (currentItem is ChatMessage && currentItem.id == messageId) {
+ return Pair(currentItem, messagePosition)
+ } else {
+ Log.d(TAG, "currentItem retrieved was not chatmessage or its id was not correct")
+ }
+ } else {
+ Log.d(
+ TAG, "messagePosition is -1, adapter # of items: " + adapter!!.itemCount
+ )
+ }
+ } else {
+ Log.d(TAG, "TalkMessagesListAdapter is null")
+ }
+ return null
+ }
+
private fun scrollToRequestedMessageIfNeeded() {
intent.getStringExtra(BundleKeys.KEY_MESSAGE_ID)?.let {
scrollToMessageWithId(it)
@@ -2771,16 +2675,21 @@ class ChatActivity :
}
override fun onLoadMore(page: Int, totalItemsCount: Int) {
- val calculatedPage = totalItemsCount / PAGE_SIZE
- if (calculatedPage > 0) {
- chatViewModel.refreshChatParams(
- setupFieldsForPullChatMessages(
- false,
- null,
- true
- )
- )
- }
+ val id = (
+ adapter?.items?.last {
+ it.item is ChatMessage
+ }?.item as ChatMessage
+ ).jsonMessageId
+
+ val urlForChatting = ApiUtils.getUrlForChat(chatApiVersion, conversationUser?.baseUrl, roomToken)
+
+ chatViewModel.loadMoreMessages(
+ beforeMessageId = id.toLong(),
+ withUrl = urlForChatting,
+ withCredentials = credentials!!,
+ withMessageLimit = MESSAGE_PULL_LIMIT,
+ roomToken = currentConversation!!.token!!
+ )
}
override fun format(date: Date): String {
@@ -2917,35 +2826,17 @@ class ChatActivity :
private fun handleSystemMessages(chatMessageList: List): List {
val chatMessageMap = chatMessageList.map { it.id to it }.toMap().toMutableMap()
+
val chatMessageIterator = chatMessageMap.iterator()
while (chatMessageIterator.hasNext()) {
val currentMessage = chatMessageIterator.next()
- // setDeletionFlagsAndRemoveInfomessages
- if (isInfoMessageAboutDeletion(currentMessage)) {
- if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)) {
- // if chatMessageMap doesn't contain message to delete (this happens when lookingIntoFuture),
- // the message to delete has to be modified directly inside the adapter
- setMessageAsDeleted(currentMessage.value.parentMessage)
- } else {
- chatMessageMap[currentMessage.value.parentMessage!!.id]!!.isDeleted = true
- }
- chatMessageIterator.remove()
- } else if (isReactionsMessage(currentMessage)) {
- // delete reactions system messages
- if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)) {
- updateAdapterForReaction(currentMessage.value.parentMessage)
- }
-
- chatMessageIterator.remove()
- } else if (isPollVotedMessage(currentMessage)) {
- // delete poll system messages
- chatMessageIterator.remove()
- } else if (isEditMessage(currentMessage)) {
- if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)) {
- setMessageAsEdited(currentMessage.value.parentMessage)
- }
+ if (isInfoMessageAboutDeletion(currentMessage) ||
+ isReactionsMessage(currentMessage) ||
+ isPollVotedMessage(currentMessage) ||
+ isEditMessage(currentMessage)
+ ) {
chatMessageIterator.remove()
}
}
@@ -2977,7 +2868,7 @@ class ChatActivity :
}
private fun isInfoMessageAboutDeletion(currentMessage: MutableMap.MutableEntry): Boolean {
- return currentMessage.value.parentMessage != null && currentMessage.value.systemMessageType == ChatMessage
+ return currentMessage.value.parentMessageId != null && currentMessage.value.systemMessageType == ChatMessage
.SystemMessageType.MESSAGE_DELETED
}
@@ -2988,7 +2879,7 @@ class ChatActivity :
}
private fun isEditMessage(currentMessage: MutableMap.MutableEntry): Boolean {
- return currentMessage.value.parentMessage != null && currentMessage.value.systemMessageType == ChatMessage
+ return currentMessage.value.parentMessageId != null && currentMessage.value.systemMessageType == ChatMessage
.SystemMessageType.MESSAGE_EDITED
}
@@ -3017,7 +2908,7 @@ class ChatActivity :
currentConversation?.let {
val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, roomToken)
- bundle.putString(KEY_ROOM_ID, roomId)
+ // 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)
@@ -3039,7 +2930,7 @@ class ChatActivity :
bundle.putBoolean(BundleKeys.KEY_CALL_WITHOUT_NOTIFICATION, true)
}
- if (it.objectType == ObjectType.ROOM) {
+ if (it.objectType == ConversationEnums.ObjectType.ROOM) {
bundle.putBoolean(KEY_IS_BREAKOUT_ROOM, true)
}
@@ -3152,7 +3043,7 @@ class ChatActivity :
val bundle = Bundle()
bundle.putBoolean(BundleKeys.KEY_FORWARD_MSG_FLAG, true)
bundle.putString(BundleKeys.KEY_FORWARD_MSG_TEXT, message?.text)
- bundle.putString(BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM, roomId)
+ bundle.putString(BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM, roomToken)
val intent = Intent(this, ConversationsListActivity::class.java)
intent.putExtras(bundle)
@@ -3285,7 +3176,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\"}"
}
when (type) {
@@ -3350,7 +3241,7 @@ class ChatActivity :
conversationUser?.userId?.isNotEmpty() == true && conversationUser!!.userId != "?" &&
message.user.id.startsWith("users/") &&
message.user.id.substring(ACTOR_LENGTH) != currentConversation?.actorId &&
- currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
+ currentConversation?.type != ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
isShowMessageDeletionButton(message) || // delete
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() || // forward
message.previousMessageId > NO_PREVIOUS_MESSAGE_ID && // mark as unread
@@ -3361,39 +3252,44 @@ class ChatActivity :
private fun setMessageAsDeleted(message: IMessage?) {
val messageTemp = message as ChatMessage
messageTemp.isDeleted = true
+ messageTemp.message = getString(R.string.message_deleted_by_you)
messageTemp.isOneToOneConversation =
- currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+ currentConversation?.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
messageTemp.activeUser = conversationUser
adapter?.update(messageTemp)
}
- private fun setMessageAsEdited(message: IMessage?) {
+ private fun setMessageAsEdited(message: IMessage?, newString: String) {
val messageTemp = message as ChatMessage
messageTemp.lastEditTimestamp = message.lastEditTimestamp
+ messageTemp.message = newString
val index = adapter?.getMessagePositionById(messageTemp.id)!!
if (index > 0) {
val adapterMsg = adapter?.items?.get(index)?.item as ChatMessage
- messageTemp.parentMessage = adapterMsg.parentMessage
+ messageTemp.parentMessageId = adapterMsg.parentMessageId
}
messageTemp.isOneToOneConversation =
- currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+ currentConversation?.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
messageTemp.activeUser = conversationUser
adapter?.update(messageTemp)
}
- private fun updateAdapterForReaction(message: IMessage?) {
- val messageTemp = message as ChatMessage
+ private fun updateMessageInsideAdapter(message: IMessage?) {
+ message?.let {
+ val messageTemp = message as ChatMessage
- messageTemp.isOneToOneConversation =
- currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
- messageTemp.activeUser = conversationUser
+ // TODO is this needed?
+ messageTemp.isOneToOneConversation =
+ currentConversation?.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+ messageTemp.activeUser = conversationUser
- adapter?.update(messageTemp)
+ adapter?.update(messageTemp)
+ }
}
fun updateUiToAddReaction(message: ChatMessage, emoji: String) {
@@ -3428,6 +3324,9 @@ class ChatActivity :
amount = 0
}
message.reactions!![emoji] = amount - 1
+ if (message.reactions!![emoji]!! <= 0) {
+ message.reactions!!.remove(emoji)
+ }
message.reactionsSelf!!.remove(emoji)
adapter?.update(message)
}
@@ -3529,7 +3428,7 @@ class ChatActivity :
@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onMessageEvent(userMentionClickEvent: UserMentionClickEvent) {
- if (currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
+ if (currentConversation?.type != ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
currentConversation?.name != userMentionClickEvent.userId
) {
var apiVersion = 1
@@ -3602,13 +3501,21 @@ class ChatActivity :
}
fun jumpToQuotedMessage(parentMessage: ChatMessage) {
+ var foundMessage = false
for (position in 0 until (adapter!!.items.size)) {
val currentItem = adapter?.items?.get(position)?.item
if (currentItem is ChatMessage && currentItem.id == parentMessage.id) {
layoutManager!!.scrollToPosition(position)
+ foundMessage = true
break
}
}
+ if (!foundMessage) {
+ Log.d(TAG, "quoted message with id " + parentMessage.id + " was not found in adapter")
+ // TODO: show better info
+ // TODO: improve handling how this can be avoided. E.g. loading chat until message is reached...
+ Snackbar.make(binding.root, "Message was not found", Snackbar.LENGTH_LONG).show()
+ }
}
override fun joinAudioCall() {
@@ -3688,6 +3595,7 @@ class ChatActivity :
private const val QUOTED_MESSAGE_IMAGE_MAX_HEIGHT = 96f
private const val MENTION_AUTO_COMPLETE_ELEVATION = 6f
private const val MESSAGE_PULL_LIMIT = 100
+ private const val PAGE_SIZE = 100
private const val INVITE_LENGTH = 6
private const val ACTOR_LENGTH = 6
private const val ANIMATION_DURATION: Long = 750
@@ -3715,6 +3623,5 @@ class ChatActivity :
private const val CURRENT_AUDIO_POSITION_KEY = "CURRENT_AUDIO_POSITION"
private const val CURRENT_AUDIO_WAS_PLAYING_KEY = "CURRENT_AUDIO_PLAYING"
private const val RESUME_AUDIO_TAG = "RESUME_AUDIO_TAG"
- private const val PAGE_SIZE = 50
}
}
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 ad21845f24..d87734cd55 100644
--- a/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt
+++ b/app/src/main/java/com/nextcloud/talk/chat/MessageInputFragment.kt
@@ -27,6 +27,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
+import android.view.animation.Animation.AnimationListener
import android.view.animation.LinearInterpolator
import android.widget.ImageButton
import android.widget.ImageView
@@ -40,6 +41,7 @@ import androidx.core.content.ContextCompat
import androidx.core.widget.doAfterTextChanged
import androidx.emoji2.widget.EmojiTextView
import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
import autodagger.AutoInjector
import coil.load
import com.google.android.flexbox.FlexboxLayout
@@ -50,10 +52,11 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.chat.viewmodels.ChatViewModel
+import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.databinding.FragmentMessageInputBinding
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
-import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.mention.Mention
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage
import com.nextcloud.talk.presenters.MentionAutocompletePresenter
@@ -70,6 +73,9 @@ import com.nextcloud.talk.utils.text.Spans
import com.otaliastudios.autocomplete.Autocomplete
import com.stfalcon.chatkit.commons.models.IMessage
import com.vanniktech.emoji.EmojiPopup
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
import java.util.Objects
import javax.inject.Inject
@@ -101,6 +107,9 @@ class MessageInputFragment : Fragment() {
@Inject
lateinit var userManager: UserManager
+ @Inject
+ lateinit var networkMonitor: NetworkMonitor
+
lateinit var binding: FragmentMessageInputBinding
private var typedWhileTypingTimerIsRunning: Boolean = false
private var typingTimer: CountDownTimer? = null
@@ -158,6 +167,79 @@ class MessageInputFragment : Fragment() {
else -> {}
}
}
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ var wasOnline = true
+ networkMonitor.isOnline.onEach { isOnline ->
+ val connectionGained = (!wasOnline && isOnline)
+ wasOnline = !binding.fragmentMessageInputView.isShown
+ Log.d(TAG, "isOnline: $isOnline\nwasOnline: $wasOnline\nconnectionGained: $connectionGained")
+
+ // FIXME timeout exception - maybe something to do with the room?
+ // handleMessageQueue(isOnline)
+ handleUI(isOnline, connectionGained)
+ }.collect()
+ }
+ }
+
+ private fun handleUI(isOnline: Boolean, connectionGained: Boolean) {
+ if (isOnline) {
+ if (connectionGained) {
+ val animation: Animation = AlphaAnimation(1.0f, 0.0f)
+ animation.duration = 3000
+ animation.interpolator = LinearInterpolator()
+ binding.fragmentConnectionLost.setBackgroundColor(resources.getColor(R.color.hwSecurityGreen))
+ binding.fragmentConnectionLost.text = getString(R.string.connection_gained)
+ binding.fragmentConnectionLost.startAnimation(animation)
+ binding.fragmentConnectionLost.animation.setAnimationListener(object : AnimationListener {
+ override fun onAnimationStart(animation: Animation?) {
+ // unused atm
+ }
+
+ override fun onAnimationEnd(animation: Animation?) {
+ binding.fragmentConnectionLost.visibility = View.GONE
+ binding.fragmentConnectionLost.setBackgroundColor(resources.getColor(R.color.hwSecurityRed))
+ binding.fragmentConnectionLost.text =
+ getString(R.string.connection_lost_sent_messages_are_queued)
+ }
+
+ override fun onAnimationRepeat(animation: Animation?) {
+ // unused atm
+ }
+ })
+ }
+
+ binding.fragmentMessageInputView.attachmentButton.isEnabled = true
+ binding.fragmentMessageInputView.recordAudioButton.isEnabled = true
+ binding.fragmentMessageInputView.messageInput.isEnabled = true
+ } else {
+ binding.fragmentConnectionLost.clearAnimation()
+ binding.fragmentConnectionLost.visibility = View.GONE
+ binding.fragmentConnectionLost.setBackgroundColor(resources.getColor(R.color.hwSecurityRed))
+ binding.fragmentConnectionLost.text =
+ getString(R.string.connection_lost_sent_messages_are_queued)
+ binding.fragmentConnectionLost.visibility = View.VISIBLE
+ binding.fragmentMessageInputView.attachmentButton.isEnabled = false
+ binding.fragmentMessageInputView.recordAudioButton.isEnabled = false
+ binding.fragmentMessageInputView.messageInput.isEnabled = false
+ }
+ }
+
+ private fun handleMessageQueue(isOnline: Boolean) {
+ if (isOnline) {
+ chatActivity.messageInputViewModel.switchToMessageQueue(false)
+ chatActivity.messageInputViewModel.sendAndEmptyMessageQueue(
+ chatActivity.roomToken,
+ chatActivity.conversationUser!!.getCredentials(),
+ ApiUtils.getUrlForChat(
+ chatActivity.chatApiVersion,
+ chatActivity.conversationUser!!.baseUrl!!,
+ chatActivity.roomToken
+ )
+ )
+ } else {
+ chatActivity.messageInputViewModel.switchToMessageQueue(true)
+ }
}
private fun restoreState() {
@@ -694,6 +776,7 @@ class MessageInputFragment : Fragment() {
private fun sendMessage(message: CharSequence, replyTo: Int?, sendWithoutNotification: Boolean) {
chatActivity.messageInputViewModel.sendChatMessage(
+ chatActivity.roomToken,
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
new file mode 100644
index 0000000000..83f79ae5e7
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/chat/data/ChatMessageRepository.kt
@@ -0,0 +1,70 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Your Name
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.chat.data
+
+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 kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+
+interface ChatMessageRepository : LifecycleAwareManager {
+
+ /**
+ * Stream of a list of messages to be handled using the associated boolean
+ * false for past messages, true for future messages.
+ */
+ val messageFlow:
+ Flow<
+ Pair<
+ Boolean,
+ List
+ >
+ >
+
+ val updateMessageFlow: Flow
+
+ val lastCommonReadFlow: Flow
+
+ fun setData(
+ conversationModel: ConversationModel,
+ credentials: String,
+ urlForChatting: String
+ )
+
+ fun loadInitialMessages(withNetworkParams: Bundle): Job
+
+ /**
+ * Loads messages from local storage. If the messages are not found, then it
+ * synchronizes the database with the server, before retrying exactly once. Only
+ * emits to [messageFlow] if the message list is not empty.
+ *
+ * [withNetworkParams] credentials and url
+ */
+ fun loadMoreMessages(
+ beforeMessageId: Long,
+ roomToken: String,
+ withMessageLimit: Int,
+ withNetworkParams: Bundle
+ ): Job
+
+ /**
+ * Long polls the server for any updates to the chat, if found, it synchronizes
+ * the database with the server and emits the new messages to [messageFlow],
+ * else it simply retries after timeout.
+ *
+ * [withNetworkParams] credentials and url.
+ */
+ fun initMessagePolling(): Job
+
+ /**
+ * Gets a individual message.
+ */
+ suspend fun getMessage(messageId: Long, bundle: Bundle): Flow
+}
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt
similarity index 68%
rename from app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt
rename to app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt
index ed16ad7886..1e228ba5ba 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt
+++ b/app/src/main/java/com/nextcloud/talk/chat/data/model/ChatMessage.kt
@@ -2,120 +2,88 @@
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Andy Scherzinger
- * SPDX-FileCopyrightText: 2022 Marcel Hibbe
+ * SPDX-FileCopyrightText: 2022-2024 Marcel Hibbe
* SPDX-FileCopyrightText: 2021 Tim Krüger
* SPDX-FileCopyrightText: 2017-2018 Mario Danic
* SPDX-License-Identifier: GPL-3.0-or-later
*/
-package com.nextcloud.talk.models.json.chat
+package com.nextcloud.talk.chat.data.model
-import android.os.Parcelable
import android.text.TextUtils
import android.util.Log
-import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonIgnore
-import com.bluelinelabs.logansquare.annotation.JsonObject
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.json.chat.ChatUtils.Companion.getParsedMessage
+import com.nextcloud.talk.models.json.chat.ReadStatus
import com.nextcloud.talk.models.json.converters.EnumSystemMessageTypeConverter
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.CapabilitiesUtil
import com.stfalcon.chatkit.commons.models.IUser
import com.stfalcon.chatkit.commons.models.MessageContentType
-import kotlinx.parcelize.Parcelize
import java.security.MessageDigest
import java.util.Date
-@Parcelize
-@JsonObject
data class ChatMessage(
- @JsonIgnore
var isGrouped: Boolean = false,
- @JsonIgnore
var isOneToOneConversation: Boolean = false,
- @JsonIgnore
var isFormerOneToOneConversation: Boolean = false,
- @JsonIgnore
var activeUser: User? = null,
- @JsonIgnore
var selectedIndividualHashMap: Map? = null,
- @JsonIgnore
var isDeleted: Boolean = false,
- @JsonField(name = ["id"])
var jsonMessageId: Int = 0,
- @JsonIgnore
var previousMessageId: Int = -1,
- @JsonField(name = ["token"])
var token: String? = null,
// guests or users
- @JsonField(name = ["actorType"])
var actorType: String? = null,
- @JsonField(name = ["actorId"])
var actorId: String? = null,
// send when crafting a message
- @JsonField(name = ["actorDisplayName"])
var actorDisplayName: String? = null,
- @JsonField(name = ["timestamp"])
var timestamp: Long = 0,
// send when crafting a message, max 1000 lines
- @JsonField(name = ["message"])
var message: String? = null,
- @JsonField(name = ["messageParameters"])
var messageParameters: HashMap>? = null,
- @JsonField(name = ["systemMessage"], typeConverter = EnumSystemMessageTypeConverter::class)
var systemMessageType: SystemMessageType? = null,
- @JsonField(name = ["isReplyable"])
var replyable: Boolean = false,
- @JsonField(name = ["parent"])
- var parentMessage: ChatMessage? = null,
+ var parentMessageId: Long? = null,
var readStatus: Enum = ReadStatus.NONE,
- @JsonField(name = ["messageType"])
var messageType: String? = null,
- @JsonField(name = ["reactions"])
var reactions: LinkedHashMap? = null,
- @JsonField(name = ["reactionsSelf"])
var reactionsSelf: ArrayList? = null,
- @JsonField(name = ["expirationTimestamp"])
var expirationTimestamp: Int = 0,
- @JsonField(name = ["markdown"])
var renderMarkdown: Boolean? = null,
- @JsonField(name = ["lastEditActorDisplayName"])
var lastEditActorDisplayName: String? = null,
- @JsonField(name = ["lastEditActorId"])
var lastEditActorId: String? = null,
- @JsonField(name = ["lastEditActorType"])
var lastEditActorType: String? = null,
- @JsonField(name = ["lastEditTimestamp"])
- var lastEditTimestamp: Long = 0,
+ var lastEditTimestamp: Long? = 0,
var isDownloadingVoiceMessage: Boolean = false,
@@ -145,7 +113,7 @@ data class ChatMessage(
var openWhenDownloaded: Boolean = true
-) : Parcelable, MessageContentType, MessageContentType.Image {
+) : MessageContentType, MessageContentType.Image {
var extractedUrlToPreview: String? = null
@@ -282,95 +250,7 @@ data class ChatMessage(
}
}
- val lastMessageDisplayText: String
- get() {
- if (getCalculateMessageType() == MessageType.REGULAR_TEXT_MESSAGE ||
- getCalculateMessageType() == MessageType.SYSTEM_MESSAGE ||
- getCalculateMessageType() == MessageType.SINGLE_LINK_MESSAGE
- ) {
- return text
- } else {
- if (MessageType.SINGLE_LINK_GIPHY_MESSAGE == getCalculateMessageType() ||
- MessageType.SINGLE_LINK_TENOR_MESSAGE == getCalculateMessageType() ||
- MessageType.SINGLE_LINK_GIF_MESSAGE == getCalculateMessageType()
- ) {
- return if (actorId == activeUser!!.userId) {
- sharedApplication!!.getString(R.string.nc_sent_a_gif_you)
- } else {
- String.format(
- sharedApplication!!.resources.getString(R.string.nc_sent_a_gif),
- getNullsafeActorDisplayName()
- )
- }
- } else if (MessageType.SINGLE_NC_ATTACHMENT_MESSAGE == getCalculateMessageType()) {
- return if (actorId == activeUser!!.userId) {
- sharedApplication!!.getString(R.string.nc_sent_an_attachment_you)
- } else {
- String.format(
- sharedApplication!!.resources.getString(R.string.nc_sent_an_attachment),
- getNullsafeActorDisplayName()
- )
- }
- } else if (MessageType.SINGLE_NC_GEOLOCATION_MESSAGE == getCalculateMessageType()) {
- return if (actorId == activeUser!!.userId) {
- sharedApplication!!.getString(R.string.nc_sent_location_you)
- } else {
- String.format(
- sharedApplication!!.resources.getString(R.string.nc_sent_location),
- getNullsafeActorDisplayName()
- )
- }
- } else if (MessageType.VOICE_MESSAGE == getCalculateMessageType()) {
- return if (actorId == activeUser!!.userId) {
- sharedApplication!!.getString(R.string.nc_sent_voice_you)
- } else {
- String.format(
- sharedApplication!!.resources.getString(R.string.nc_sent_voice),
- getNullsafeActorDisplayName()
- )
- }
- } else if (MessageType.SINGLE_LINK_AUDIO_MESSAGE == getCalculateMessageType()) {
- return if (actorId == activeUser!!.userId) {
- sharedApplication!!.getString(R.string.nc_sent_an_audio_you)
- } else {
- String.format(
- sharedApplication!!.resources.getString(R.string.nc_sent_an_audio),
- getNullsafeActorDisplayName()
- )
- }
- } else if (MessageType.SINGLE_LINK_VIDEO_MESSAGE == getCalculateMessageType()) {
- return if (actorId == activeUser!!.userId) {
- sharedApplication!!.getString(R.string.nc_sent_a_video_you)
- } else {
- String.format(
- sharedApplication!!.resources.getString(R.string.nc_sent_a_video),
- getNullsafeActorDisplayName()
- )
- }
- } else if (MessageType.SINGLE_LINK_IMAGE_MESSAGE == getCalculateMessageType()) {
- return if (actorId == activeUser!!.userId) {
- sharedApplication!!.getString(R.string.nc_sent_an_image_you)
- } else {
- String.format(
- sharedApplication!!.resources.getString(R.string.nc_sent_an_image),
- getNullsafeActorDisplayName()
- )
- }
- } else if (MessageType.POLL_MESSAGE == getCalculateMessageType()) {
- return if (actorId == activeUser!!.userId) {
- sharedApplication!!.getString(R.string.nc_sent_poll_you)
- } else {
- String.format(
- sharedApplication!!.resources.getString(R.string.nc_sent_poll),
- getNullsafeActorDisplayName()
- )
- }
- }
- }
- return ""
- }
-
- private fun getNullsafeActorDisplayName() =
+ fun getNullsafeActorDisplayName() =
if (!TextUtils.isEmpty(actorDisplayName)) {
actorDisplayName
} else {
diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt
similarity index 97%
rename from app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt
rename to app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt
index bd85f5fb07..f182929174 100644
--- a/app/src/main/java/com/nextcloud/talk/chat/data/ChatRepository.kt
+++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/ChatNetworkDataSource.kt
@@ -4,7 +4,7 @@
* SPDX-FileCopyrightText: 2023 Marcel Hibbe
* SPDX-License-Identifier: GPL-3.0-or-later
*/
-package com.nextcloud.talk.chat.data
+package com.nextcloud.talk.chat.data.network
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
@@ -19,7 +19,7 @@ import io.reactivex.Observable
import retrofit2.Response
@Suppress("LongParameterList", "TooManyFunctions")
-interface ChatRepository {
+interface ChatNetworkDataSource {
fun getRoom(user: User, roomToken: String): Observable
fun getCapabilities(user: User, roomToken: String): Observable
fun joinRoom(user: User, roomToken: String, roomPassword: 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
new file mode 100644
index 0000000000..fd3a4f4ab9
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/OfflineFirstChatRepository.kt
@@ -0,0 +1,623 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-FileCopyrightText: 2024 Julius Linus
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.chat.data.network
+
+import android.os.Bundle
+import android.util.Log
+import com.nextcloud.talk.chat.data.ChatMessageRepository
+import com.nextcloud.talk.chat.data.model.ChatMessage
+import com.nextcloud.talk.data.database.dao.ChatBlocksDao
+import com.nextcloud.talk.data.database.dao.ChatMessagesDao
+import com.nextcloud.talk.data.database.mappers.asEntity
+import com.nextcloud.talk.data.database.mappers.asModel
+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.models.domain.ConversationModel
+import com.nextcloud.talk.models.json.chat.ChatMessageJson
+import com.nextcloud.talk.models.json.chat.ChatOverall
+import com.nextcloud.talk.utils.bundle.BundleKeys
+import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
+import com.nextcloud.talk.utils.preferences.AppPreferences
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+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
+) : ChatMessageRepository {
+
+ val currentUser: User = userProvider.currentUser.blockingGet()
+
+ override val messageFlow:
+ Flow<
+ Pair<
+ Boolean,
+ List
+ >
+ >
+ get() = _messageFlow
+
+ private val _messageFlow:
+ MutableSharedFlow<
+ Pair<
+ Boolean,
+ List
+ >
+ > = MutableSharedFlow()
+
+ override val updateMessageFlow:
+ Flow
+ get() = _updateMessageFlow
+
+ private val _updateMessageFlow:
+ MutableSharedFlow = MutableSharedFlow()
+
+ override val lastCommonReadFlow:
+ Flow
+ get() = _lastCommonReadFlow
+
+ private val _lastCommonReadFlow:
+ MutableSharedFlow = MutableSharedFlow()
+
+ private var newXChatLastCommonRead: Int? = null
+ private var itIsPaused = false
+ private val scope = CoroutineScope(Dispatchers.IO)
+
+ lateinit var internalConversationId: String
+ private lateinit var conversationModel: ConversationModel
+ private lateinit var credentials: String
+ private lateinit var urlForChatting: String
+
+ override fun setData(
+ conversationModel: ConversationModel,
+ credentials: String,
+ urlForChatting: String
+ ) {
+ this.conversationModel = conversationModel
+ this.credentials = credentials
+ this.urlForChatting = urlForChatting
+ internalConversationId = conversationModel.internalId
+ }
+
+ override fun loadInitialMessages(withNetworkParams: Bundle): Job =
+ scope.launch {
+ Log.d(TAG, "---- loadInitialMessages ------------")
+
+ newXChatLastCommonRead = conversationModel.lastCommonReadMessage
+
+ val fieldMap = getFieldMap(
+ lookIntoFuture = false,
+ includeLastKnown = true,
+ setReadMarker = true,
+ lastKnown = null
+ )
+ withNetworkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
+ withNetworkParams.putString(BundleKeys.KEY_ROOM_TOKEN, conversationModel.token)
+
+ sync(withNetworkParams)
+
+ Log.d(TAG, "newestMessageId after sync: " + chatDao.getNewestMessageId(internalConversationId))
+
+ showLast100MessagesBeforeAndEqual(
+ internalConversationId,
+ chatDao.getNewestMessageId(internalConversationId)
+ )
+ updateUiForLastCommonRead(200)
+
+ initMessagePolling()
+ }
+
+ private fun updateUiForLastCommonRead(delay: Long) {
+ scope.launch {
+ // delay is a dirty workaround to make sure messages are added to adapter on initial load before setting
+ // their read status(otherwise there is a race condition between adding messages and setting their read
+ // status).
+ if (delay > 0) {
+ delay(delay)
+ }
+ newXChatLastCommonRead?.let {
+ _lastCommonReadFlow.emit(it)
+ }
+ }
+ }
+
+ override fun loadMoreMessages(
+ beforeMessageId: Long,
+ roomToken: String,
+ withMessageLimit: Int,
+ withNetworkParams: Bundle
+ ): Job =
+ scope.launch {
+ Log.d(TAG, "---- loadMoreMessages for $beforeMessageId ------------")
+
+ val fieldMap = getFieldMap(
+ lookIntoFuture = false,
+ includeLastKnown = false,
+ setReadMarker = true,
+ lastKnown = beforeMessageId.toInt()
+ )
+ withNetworkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
+
+ val loadFromServer = hasToLoadPreviousMessagesFromServer(beforeMessageId)
+
+ if (loadFromServer) {
+ sync(withNetworkParams)
+ }
+
+ showLast100MessagesBefore(internalConversationId, beforeMessageId)
+ updateUiForLastCommonRead(0)
+ }
+
+ override fun initMessagePolling(): Job =
+ scope.launch {
+ Log.d(TAG, "---- initMessagePolling ------------")
+
+ val initialMessageId = chatDao.getNewestMessageId(internalConversationId).toInt()
+ Log.d(TAG, "newestMessage: $initialMessageId")
+
+ var fieldMap = getFieldMap(
+ lookIntoFuture = true,
+ includeLastKnown = false,
+ setReadMarker = true,
+ lastKnown = initialMessageId
+ )
+
+ val networkParams = Bundle()
+
+ while (!itIsPaused) {
+ if (!monitor.isOnline.first()) Thread.sleep(500)
+
+ // sync database with server (This is a long blocking call because long polling (lookIntoFuture) is set)
+ networkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
+
+ val resultsFromSync = sync(networkParams)
+ if (!resultsFromSync.isNullOrEmpty()) {
+ val chatMessages = resultsFromSync.map(ChatMessageEntity::asModel)
+ val pair = Pair(true, chatMessages)
+ _messageFlow.emit(pair)
+ }
+
+ updateUiForLastCommonRead(0)
+
+ val newestMessage = chatDao.getNewestMessageId(internalConversationId).toInt()
+
+ // update field map vars for next cycle
+ fieldMap = getFieldMap(
+ lookIntoFuture = true,
+ includeLastKnown = false,
+ setReadMarker = true,
+ lastKnown = newestMessage
+ )
+ }
+ }
+
+ private suspend fun hasToLoadPreviousMessagesFromServer(
+ beforeMessageId: Long
+ ): Boolean {
+ val loadFromServer: Boolean
+
+ val blockForMessage = getBlockOfMessage(beforeMessageId.toInt())
+
+ if (blockForMessage == null) {
+ Log.d(TAG, "No blocks for this message were found so we have to ask server")
+ loadFromServer = true
+ } else if (!blockForMessage.hasHistory) {
+ Log.d(TAG, "The last chatBlock is reached so we won't request server for older messages")
+ loadFromServer = false
+ } else {
+ // we know that beforeMessageId and blockForMessage.oldestMessageId are in the same block.
+ // As we want the last 100 entries before beforeMessageId, we calculate if these messages are 100
+ // entries apart from each other
+
+ val amountBetween = chatDao.getCountBetweenMessageIds(
+ internalConversationId,
+ beforeMessageId,
+ blockForMessage.oldestMessageId
+ )
+ loadFromServer = amountBetween < 100
+
+ Log.d(
+ TAG, "Amount between messageId " + beforeMessageId + " and " + blockForMessage.oldestMessageId +
+ " is: " + amountBetween + " so 'loadFromServer' is " + loadFromServer
+ )
+ }
+ return loadFromServer
+ }
+
+ private fun getFieldMap(
+ lookIntoFuture: Boolean,
+ includeLastKnown: Boolean,
+ setReadMarker: Boolean,
+ lastKnown: Int?
+ ): HashMap {
+ val fieldMap = HashMap()
+
+ fieldMap["includeLastKnown"] = if (includeLastKnown) 1 else 0
+
+ if (lastKnown != null) {
+ fieldMap["lastKnownMessageId"] = lastKnown
+ }
+
+ newXChatLastCommonRead?.let {
+ fieldMap["lastCommonReadId"] = it
+ }
+
+ fieldMap["timeout"] = if (lookIntoFuture) 30 else 0
+ fieldMap["limit"] = 100
+ fieldMap["lookIntoFuture"] = if (lookIntoFuture) 1 else 0
+ fieldMap["setReadMarker"] = if (setReadMarker) 1 else 0
+
+ return fieldMap
+ }
+
+ override suspend fun getMessage(messageId: Long, bundle: Bundle):
+ Flow {
+
+ Log.d(TAG, "Get message with id $messageId")
+ val loadFromServer = hasToLoadPreviousMessagesFromServer(messageId)
+
+ if (loadFromServer) {
+ val fieldMap = getFieldMap(
+ lookIntoFuture = false,
+ includeLastKnown = true,
+ setReadMarker = false,
+ lastKnown = messageId.toInt()
+ )
+ bundle.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
+
+ // Although only the single message will be returned, a server request will load 100 messages.
+ // If this turns out to be confusion for debugging we could load set the limit to 1 for this request.
+ sync(bundle)
+ }
+ return chatDao.getChatMessageForConversation(internalConversationId, messageId)
+ .map(ChatMessageEntity::asModel)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun getMessagesFromServer(bundle: Bundle): Pair>? {
+ Log.d(TAG, "An online request is made!!!!!!!!!!!!!!!!!!!!")
+ val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap
+
+ try {
+ val result = network.pullChatMessages(credentials, urlForChatting, fieldMap)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ // .timeout(3, TimeUnit.SECONDS)
+ .map { it ->
+ when (it.code()) {
+ HTTP_CODE_OK -> {
+ Log.d(TAG, "getMessagesFromServer HTTP_CODE_OK")
+ newXChatLastCommonRead = it.headers()["X-Chat-Last-Common-Read"]?.let {
+ Integer.parseInt(it)
+ }
+
+ return@map Pair(
+ HTTP_CODE_OK,
+ (it.body() as ChatOverall).ocs!!.data!!
+ )
+ }
+
+ HTTP_CODE_NOT_MODIFIED -> {
+ Log.d(TAG, "getMessagesFromServer HTTP_CODE_NOT_MODIFIED")
+
+ return@map Pair(
+ HTTP_CODE_NOT_MODIFIED,
+ listOf()
+ )
+ }
+
+ HTTP_CODE_PRECONDITION_FAILED -> {
+ Log.d(TAG, "getMessagesFromServer HTTP_CODE_PRECONDITION_FAILED")
+
+ return@map Pair(
+ HTTP_CODE_PRECONDITION_FAILED,
+ listOf()
+ )
+ }
+
+ else -> {
+ return@map Pair(
+ HTTP_CODE_PRECONDITION_FAILED,
+ listOf()
+ )
+ }
+ }
+ }
+ .blockingSingle()
+ return result
+ } catch (e: Exception) {
+ Log.e(TAG, "Something went wrong when pulling chat messages", e)
+ }
+ return null
+ }
+
+ private suspend fun sync(bundle: Bundle): List? {
+ if (!monitor.isOnline.first()) {
+ Log.d(TAG, "Device is offline, can't load chat messages from server")
+ return null
+ }
+
+ val result = getMessagesFromServer(bundle) ?: return listOf()
+ var chatMessagesFromSync: List? = null
+
+ val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap
+ val queriedMessageId = fieldMap["lastKnownMessageId"]
+ val lookIntoFuture = fieldMap["lookIntoFuture"] == 1
+
+ val statusCode = result.first
+
+ val hasHistory = getHasHistory(statusCode, lookIntoFuture)
+
+ Log.d(
+ TAG,
+ "internalConv=$internalConversationId statusCode=$statusCode lookIntoFuture=$lookIntoFuture " +
+ "hasHistory=$hasHistory " +
+ "queriedMessageId=$queriedMessageId"
+ )
+
+ val blockContainingQueriedMessage: ChatBlockEntity? = getBlockOfMessage(queriedMessageId)
+
+ if (blockContainingQueriedMessage != null && !hasHistory) {
+ blockContainingQueriedMessage.hasHistory = false
+ chatBlocksDao.upsertChatBlock(blockContainingQueriedMessage)
+ Log.d(TAG, "End of chat was reached so hasHistory=false is set")
+ }
+
+ if (result.second.isNotEmpty()) {
+ val chatMessagesJson = result.second
+
+ handleUpdateMessages(chatMessagesJson)
+
+ chatMessagesFromSync = chatMessagesJson.map {
+ it.asEntity(currentUser.id!!)
+ }
+
+ chatDao.upsertChatMessages(chatMessagesFromSync)
+
+ val oldestIdFromSync = chatMessagesFromSync.minByOrNull { it.id }!!.id
+ val newestIdFromSync = chatMessagesFromSync.maxByOrNull { it.id }!!.id
+ Log.d(TAG, "oldestIdFromSync: $oldestIdFromSync")
+ Log.d(TAG, "newestIdFromSync: $newestIdFromSync")
+
+ var oldestMessageIdForNewChatBlock = oldestIdFromSync
+ var newestMessageIdForNewChatBlock = newestIdFromSync
+
+ if (blockContainingQueriedMessage != null) {
+ if (lookIntoFuture) {
+ val oldestMessageIdFromBlockOfQueriedMessage = blockContainingQueriedMessage.oldestMessageId
+ Log.d(TAG, "oldestMessageIdFromBlockOfQueriedMessage: $oldestMessageIdFromBlockOfQueriedMessage")
+ oldestMessageIdForNewChatBlock = oldestMessageIdFromBlockOfQueriedMessage
+ } else {
+ val newestMessageIdFromBlockOfQueriedMessage = blockContainingQueriedMessage.newestMessageId
+ Log.d(TAG, "newestMessageIdFromBlockOfQueriedMessage: $newestMessageIdFromBlockOfQueriedMessage")
+ newestMessageIdForNewChatBlock = newestMessageIdFromBlockOfQueriedMessage
+ }
+ }
+
+ Log.d(TAG, "oldestMessageIdForNewChatBlock: $oldestMessageIdForNewChatBlock")
+ Log.d(TAG, "newestMessageIdForNewChatBlock: $newestMessageIdForNewChatBlock")
+
+ val newChatBlock = ChatBlockEntity(
+ internalConversationId = internalConversationId,
+ accountId = conversationModel.accountId,
+ token = conversationModel.token,
+ oldestMessageId = oldestMessageIdForNewChatBlock,
+ newestMessageId = newestMessageIdForNewChatBlock,
+ hasHistory = hasHistory
+ )
+ chatBlocksDao.upsertChatBlock(newChatBlock)
+
+ updateBlocks(newChatBlock)
+ } else {
+ Log.d(TAG, "no data is updated...")
+ }
+
+ return chatMessagesFromSync
+ }
+
+ private suspend fun handleUpdateMessages(messagesJson: List) {
+ messagesJson.forEach { messageJson ->
+ when (messageJson.systemMessageType) {
+ ChatMessage.SystemMessageType.REACTION,
+ ChatMessage.SystemMessageType.REACTION_REVOKED,
+ ChatMessage.SystemMessageType.REACTION_DELETED,
+ ChatMessage.SystemMessageType.MESSAGE_DELETED,
+ ChatMessage.SystemMessageType.POLL_VOTED,
+ ChatMessage.SystemMessageType.MESSAGE_EDITED -> {
+ // the parent message is always the newest state, no matter how old the system message is.
+ // that's why we can just take the parent, update it in DB and update the UI
+ messageJson.parentMessage?.let { parentMessageJson ->
+ val parentMessageEntity = parentMessageJson.asEntity(currentUser.id!!)
+ chatDao.upsertChatMessage(parentMessageEntity)
+ _updateMessageFlow.emit(parentMessageEntity.asModel())
+ }
+ }
+
+ ChatMessage.SystemMessageType.CLEARED_CHAT -> {
+ // for lookIntoFuture just deleting everything would be fine.
+ // But lets say we did not open the chat for a while and in between it was cleared.
+ // We just load the last 100 messages but this don't contain the system message.
+ // We scroll up and load the system message. Deleting everything is not an option as we
+ // would loose the messages that we want to keep. We only want to
+ // delete the messages and chatBlocks older than the system message.
+ chatDao.deleteMessagesOlderThan(internalConversationId, messageJson.id)
+ chatBlocksDao.deleteChatBlocksOlderThan(internalConversationId, messageJson.id)
+ }
+
+ else -> {}
+ }
+ }
+ }
+
+ /**
+ * 304 is returned when oldest message of chat was queried or when long polling request returned with no
+ * modification. hasHistory is only set to false, when 304 was returned for the the oldest message
+ */
+ private fun getHasHistory(statusCode: Int, lookIntoFuture: Boolean): Boolean {
+ return if (statusCode == HTTP_CODE_NOT_MODIFIED) {
+ lookIntoFuture
+ } else {
+ true
+ }
+ }
+
+ private suspend fun getBlockOfMessage(queriedMessageId: Int?): ChatBlockEntity? {
+ var blockContainingQueriedMessage: ChatBlockEntity? = null
+ if (queriedMessageId != null) {
+ val blocksContainingQueriedMessage =
+ chatBlocksDao.getChatBlocksContainingMessageId(internalConversationId, queriedMessageId.toLong())
+
+ val chatBlocks = blocksContainingQueriedMessage.first()
+ if (chatBlocks.size > 1) {
+ Log.w(TAG, "multiple chat blocks with messageId $queriedMessageId were found")
+ }
+
+ blockContainingQueriedMessage = if (chatBlocks.isNotEmpty()) {
+ chatBlocks.first()
+ } else {
+ null
+ }
+ }
+ return blockContainingQueriedMessage
+ }
+
+ private suspend fun updateBlocks(chatBlock: ChatBlockEntity): ChatBlockEntity? {
+ val connectedChatBlocks =
+ chatBlocksDao.getConnectedChatBlocks(
+ internalConversationId,
+ chatBlock.oldestMessageId,
+ chatBlock.newestMessageId
+ ).first()
+
+ if (connectedChatBlocks.size == 1) {
+ Log.d(TAG, "This chatBlock is not connected to others")
+ val chatBlockFromDb = connectedChatBlocks[0]
+ Log.d(TAG, "chatBlockFromDb.oldestMessageId: " + chatBlockFromDb.oldestMessageId)
+ Log.d(TAG, "chatBlockFromDb.newestMessageId: " + chatBlockFromDb.newestMessageId)
+ return chatBlockFromDb
+ } else if (connectedChatBlocks.size > 1) {
+ Log.d(TAG, "Found " + connectedChatBlocks.size + " chat blocks that are connected")
+ val oldestIdFromDbChatBlocks =
+ connectedChatBlocks.minByOrNull { it.oldestMessageId }!!.oldestMessageId
+ val newestIdFromDbChatBlocks =
+ connectedChatBlocks.maxByOrNull { it.newestMessageId }!!.newestMessageId
+
+ val hasNoHistory = connectedChatBlocks.any { !it.hasHistory }
+ val hasHistory = !hasNoHistory
+ Log.d(TAG, "hasHistory = $hasHistory")
+
+ chatBlocksDao.deleteChatBlocks(connectedChatBlocks)
+ Log.d(TAG, "These chat blocks were deleted")
+
+ val newChatBlock = ChatBlockEntity(
+ internalConversationId = internalConversationId,
+ accountId = conversationModel.accountId,
+ token = conversationModel.token,
+ oldestMessageId = oldestIdFromDbChatBlocks,
+ newestMessageId = newestIdFromDbChatBlocks,
+ hasHistory = hasHistory
+ )
+ chatBlocksDao.upsertChatBlock(newChatBlock)
+ Log.d(TAG, "A new chat block was created that covers all the range of the found chatblocks")
+ Log.d(TAG, "new chatBlock - oldest MessageId: $oldestIdFromDbChatBlocks")
+ Log.d(TAG, "new chatBlock - newest MessageId: $newestIdFromDbChatBlocks")
+ return newChatBlock
+ } else {
+ Log.d(TAG, "No chat block found ....")
+ return null
+ }
+ }
+
+ private suspend fun showLast100MessagesBeforeAndEqual(internalConversationId: String, messageId: Long) {
+ 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,
+ internalConversationId,
+ 100
+ )
+
+ if (list.isNotEmpty()) {
+ val pair = Pair(false, list)
+ _messageFlow.emit(pair)
+ }
+ }
+
+ private suspend fun showLast100MessagesBefore(internalConversationId: String, messageId: Long) {
+ suspend fun getMessagesBefore(
+ messageId: Long,
+ internalConversationId: String,
+ messageLimit: Int
+ ): List =
+ chatDao.getMessagesForConversationBefore(
+ internalConversationId,
+ messageId,
+ messageLimit
+ ).map {
+ it.map(ChatMessageEntity::asModel)
+ }.first()
+
+ val list = getMessagesBefore(
+ messageId,
+ internalConversationId,
+ 100
+ )
+
+ if (list.isNotEmpty()) {
+ val pair = Pair(false, list)
+ _messageFlow.emit(pair)
+ }
+ }
+
+ override fun handleOnPause() {
+ itIsPaused = true
+ }
+
+ override fun handleOnResume() {
+ itIsPaused = false
+ }
+
+ override fun handleOnStop() {
+ // unused atm
+ }
+
+ companion object {
+ val TAG = OfflineFirstChatRepository::class.simpleName
+ private const val HTTP_CODE_OK: Int = 200
+ private const val HTTP_CODE_NOT_MODIFIED = 304
+ private const val HTTP_CODE_PRECONDITION_FAILED = 412
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/chat/data/network/NetworkChatRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt
similarity index 97%
rename from app/src/main/java/com/nextcloud/talk/chat/data/network/NetworkChatRepositoryImpl.kt
rename to app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt
index b922f6c2c7..774a6d423f 100644
--- a/app/src/main/java/com/nextcloud/talk/chat/data/network/NetworkChatRepositoryImpl.kt
+++ b/app/src/main/java/com/nextcloud/talk/chat/data/network/RetrofitChatNetwork.kt
@@ -7,7 +7,6 @@
package com.nextcloud.talk.chat.data.network
import com.nextcloud.talk.api.NcApi
-import com.nextcloud.talk.chat.data.ChatRepository
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
@@ -21,7 +20,7 @@ import com.nextcloud.talk.utils.ApiUtils
import io.reactivex.Observable
import retrofit2.Response
-class NetworkChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository {
+class RetrofitChatNetwork(private val ncApi: NcApi) : ChatNetworkDataSource {
override fun getRoom(user: User, roomToken: String): Observable {
val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
@@ -29,7 +28,7 @@ class NetworkChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository {
return ncApi.getRoom(
credentials,
ApiUtils.getUrlForRoom(apiVersion, user.baseUrl!!, roomToken)
- ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
+ ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!, user) }
}
override fun getCapabilities(user: User, roomToken: String): Observable {
@@ -50,7 +49,7 @@ class NetworkChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository {
credentials,
ApiUtils.getUrlForParticipantsActive(apiVersion, user.baseUrl!!, roomToken),
roomPassword
- ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
+ ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!, user) }
}
override fun setReminder(
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 58eaa463a0..7f06b84cf4 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
@@ -8,22 +8,25 @@ package com.nextcloud.talk.chat.viewmodels
import android.content.Context
import android.net.Uri
+import android.os.Bundle
import android.util.Log
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import com.nextcloud.talk.chat.data.ChatRepository
+import com.nextcloud.talk.chat.data.ChatMessageRepository
import com.nextcloud.talk.chat.data.io.AudioFocusRequestManager
import com.nextcloud.talk.chat.data.io.MediaRecorderManager
+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.jobs.UploadAndShareFilesWorker
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
-import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
@@ -31,20 +34,30 @@ import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.reminder.Reminder
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.utils.ConversationUtils
+import com.nextcloud.talk.utils.bundle.BundleKeys
+import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
-import retrofit2.Response
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.onEach
import java.io.File
import javax.inject.Inject
@Suppress("TooManyFunctions", "LongParameterList")
class ChatViewModel @Inject constructor(
- private val chatRepository: ChatRepository,
+ // should be removed here. Use it via RetrofitChatNetwork
+ private val chatNetworkDataSource: ChatNetworkDataSource,
+ private val chatRepository: ChatMessageRepository,
+ private val conversationRepository: OfflineConversationsRepository,
private val reactionsRepository: ReactionsRepository,
private val mediaRecorderManager: MediaRecorderManager,
- private val audioFocusRequestManager: AudioFocusRequestManager
+ private val audioFocusRequestManager: AudioFocusRequestManager,
+ private val userProvider: CurrentUserProviderNew
) : ViewModel(), DefaultLifecycleObserver {
enum class LifeCycleFlag {
@@ -52,6 +65,7 @@ class ChatViewModel @Inject constructor(
RESUMED,
STOPPED
}
+
lateinit var currentLifeCycleFlag: LifeCycleFlag
val disposableSet = mutableSetOf()
@@ -59,6 +73,7 @@ class ChatViewModel @Inject constructor(
super.onResume(owner)
currentLifeCycleFlag = LifeCycleFlag.RESUMED
mediaRecorderManager.handleOnResume()
+ chatRepository.handleOnResume()
}
override fun onPause(owner: LifecycleOwner) {
@@ -67,13 +82,16 @@ class ChatViewModel @Inject constructor(
disposableSet.forEach { disposable -> disposable.dispose() }
disposableSet.clear()
mediaRecorderManager.handleOnPause()
+ chatRepository.handleOnPause()
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
currentLifeCycleFlag = LifeCycleFlag.STOPPED
mediaRecorderManager.handleOnStop()
+ chatRepository.handleOnStop()
}
+
val getAudioFocusChange: LiveData
get() = audioFocusRequestManager.getManagerState
@@ -89,9 +107,28 @@ class ChatViewModel @Inject constructor(
val getVoiceRecordingLocked: LiveData
get() = _getVoiceRecordingLocked
- private val _getFieldMapForChat: MutableLiveData> = MutableLiveData()
- val getFieldMapForChat: LiveData>
- get() = _getFieldMapForChat
+ val getMessageFlow = chatRepository.messageFlow
+ .onEach {
+ _chatMessageViewState.value = if (_chatMessageViewState.value == ChatMessageInitialState) {
+ ChatMessageStartState
+ } else {
+ ChatMessageUpdateState
+ }
+ }.catch {
+ _chatMessageViewState.value = ChatMessageErrorState
+ }
+
+ val getUpdateMessageFlow = chatRepository.updateMessageFlow
+
+ val getLastCommonReadFlow = chatRepository.lastCommonReadFlow
+
+ val getConversationFlow = conversationRepository.conversationFlow
+ .onEach {
+ _getRoomViewState.value = GetRoomSuccessState
+ }.catch {
+ _getRoomViewState.value = GetRoomErrorState
+ }
+
sealed interface ViewState
object GetReminderStartState : ViewState
@@ -111,7 +148,7 @@ class ChatViewModel @Inject constructor(
object GetRoomStartState : ViewState
object GetRoomErrorState : ViewState
- open class GetRoomSuccessState(val conversationModel: ConversationModel) : ViewState
+ object GetRoomSuccessState : ViewState
private val _getRoomViewState: MutableLiveData = MutableLiveData(GetRoomStartState)
val getRoomViewState: LiveData
@@ -136,28 +173,24 @@ class ChatViewModel @Inject constructor(
object LeaveRoomStartState : ViewState
class LeaveRoomSuccessState(val funToCallWhenLeaveSuccessful: (() -> Unit)?) : ViewState
+
private val _leaveRoomViewState: MutableLiveData = MutableLiveData(LeaveRoomStartState)
val leaveRoomViewState: LiveData
get() = _leaveRoomViewState
- object SendChatMessageStartState : ViewState
- class SendChatMessageSuccessState(val message: CharSequence) : ViewState
- class SendChatMessageErrorState(val e: Throwable, val message: CharSequence) : ViewState
- private val _sendChatMessageViewState: MutableLiveData = MutableLiveData(SendChatMessageStartState)
- val sendChatMessageViewState: LiveData
- get() = _sendChatMessageViewState
-
- object PullChatMessageStartState : ViewState
- class PullChatMessageSuccessState(val response: Response<*>, val lookIntoFuture: Boolean) : ViewState
- object PullChatMessageErrorState : ViewState
- object PullChatMessageCompleteState : ViewState
- private val _pullChatMessageViewState: MutableLiveData = MutableLiveData(PullChatMessageStartState)
- val pullChatMessageViewState: LiveData
- get() = _pullChatMessageViewState
+ object ChatMessageInitialState : ViewState
+ object ChatMessageStartState : ViewState
+ object ChatMessageUpdateState : ViewState
+ object ChatMessageErrorState : ViewState
+
+ private val _chatMessageViewState: MutableLiveData = MutableLiveData(ChatMessageInitialState)
+ val chatMessageViewState: LiveData
+ get() = _chatMessageViewState
object DeleteChatMessageStartState : ViewState
class DeleteChatMessageSuccessState(val msg: ChatOverallSingleMessage) : ViewState
object DeleteChatMessageErrorState : ViewState
+
private val _deleteChatMessageViewState: MutableLiveData = MutableLiveData(DeleteChatMessageStartState)
val deleteChatMessageViewState: LiveData
get() = _deleteChatMessageViewState
@@ -172,29 +205,38 @@ class ChatViewModel @Inject constructor(
object ReactionAddedStartState : ViewState
class ReactionAddedSuccessState(val reactionAddedModel: ReactionAddedModel) : ViewState
+
private val _reactionAddedViewState: MutableLiveData = MutableLiveData(ReactionAddedStartState)
val reactionAddedViewState: LiveData
get() = _reactionAddedViewState
object ReactionDeletedStartState : ViewState
class ReactionDeletedSuccessState(val reactionDeletedModel: ReactionDeletedModel) : ViewState
+
private val _reactionDeletedViewState: MutableLiveData = MutableLiveData(ReactionDeletedStartState)
val reactionDeletedViewState: LiveData
get() = _reactionDeletedViewState
- fun refreshChatParams(pullChatMessagesFieldMap: HashMap, overrideRefresh: Boolean = false) {
- if (pullChatMessagesFieldMap != _getFieldMapForChat.value || overrideRefresh) {
- _getFieldMapForChat.postValue(pullChatMessagesFieldMap)
- Log.d(TAG, "FieldMap Refreshed with $pullChatMessagesFieldMap vs ${_getFieldMapForChat.value}")
- }
+ fun setData(
+ conversationModel: ConversationModel,
+ credentials: String,
+ urlForChatting: String
+ ) {
+ chatRepository.setData(
+ conversationModel,
+ credentials,
+ urlForChatting
+ )
}
fun getRoom(user: User, token: String) {
_getRoomViewState.value = GetRoomStartState
- chatRepository.getRoom(user, token)
- .subscribeOn(Schedulers.io())
- ?.observeOn(AndroidSchedulers.mainThread())
- ?.subscribe(GetRoomObserver())
+ conversationRepository.getConversationSettings(token)
+
+ // chatNetworkDataSource.getRoom(user, token)
+ // .subscribeOn(Schedulers.io())
+ // ?.observeOn(AndroidSchedulers.mainThread())
+ // ?.subscribe(GetRoomObserver())
}
fun getCapabilities(user: User, token: String, conversationModel: ConversationModel) {
@@ -208,7 +250,7 @@ class ChatViewModel @Inject constructor(
_getCapabilitiesViewState.value = GetCapabilitiesUpdateState(user.capabilities!!.spreedCapability!!)
}
} else {
- chatRepository.getCapabilities(user, token)
+ chatNetworkDataSource.getCapabilities(user, token)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer {
@@ -238,7 +280,7 @@ class ChatViewModel @Inject constructor(
fun joinRoom(user: User, token: String, roomPassword: String) {
_joinRoomViewState.value = JoinRoomStartState
- chatRepository.joinRoom(user, token, roomPassword)
+ chatNetworkDataSource.joinRoom(user, token, roomPassword)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.retry(JOIN_ROOM_RETRY_COUNT)
@@ -246,21 +288,21 @@ class ChatViewModel @Inject constructor(
}
fun setReminder(user: User, roomToken: String, messageId: String, timestamp: Int, chatApiVersion: Int) {
- chatRepository.setReminder(user, roomToken, messageId, timestamp, chatApiVersion)
+ chatNetworkDataSource.setReminder(user, roomToken, messageId, timestamp, chatApiVersion)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(SetReminderObserver())
}
fun getReminder(user: User, roomToken: String, messageId: String, chatApiVersion: Int) {
- chatRepository.getReminder(user, roomToken, messageId, chatApiVersion)
+ chatNetworkDataSource.getReminder(user, roomToken, messageId, chatApiVersion)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(GetReminderObserver())
}
fun deleteReminder(user: User, roomToken: String, messageId: String, chatApiVersion: Int) {
- chatRepository.deleteReminder(user, roomToken, messageId, chatApiVersion)
+ chatNetworkDataSource.deleteReminder(user, roomToken, messageId, chatApiVersion)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer {
@@ -284,7 +326,7 @@ class ChatViewModel @Inject constructor(
fun leaveRoom(credentials: String, url: String, funToCallWhenLeaveSuccessful: (() -> Unit)?) {
val startNanoTime = System.nanoTime()
- chatRepository.leaveRoom(credentials, url)
+ chatNetworkDataSource.leaveRoom(credentials, url)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer {
@@ -309,7 +351,7 @@ class ChatViewModel @Inject constructor(
}
fun createRoom(credentials: String, url: String, queryMap: Map) {
- chatRepository.createRoom(credentials, url, queryMap)
+ chatNetworkDataSource.createRoom(credentials, url, queryMap)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer {
@@ -332,72 +374,42 @@ class ChatViewModel @Inject constructor(
})
}
- fun sendChatMessage(
- credentials: String,
- url: String,
- message: CharSequence,
- displayName: String,
- replyTo: Int,
- sendWithoutNotification: Boolean
- ) {
- chatRepository.sendChatMessage(
- credentials,
- url,
- message,
- displayName,
- replyTo,
- sendWithoutNotification
- ).subscribeOn(Schedulers.io())
- ?.observeOn(AndroidSchedulers.mainThread())
- ?.subscribe(object : Observer {
- override fun onSubscribe(d: Disposable) {
- disposableSet.add(d)
- }
-
- override fun onError(e: Throwable) {
- _sendChatMessageViewState.value = SendChatMessageErrorState(e, message)
- }
-
- override fun onComplete() {
- // unused atm
- }
-
- override fun onNext(t: GenericOverall) {
- _sendChatMessageViewState.value = SendChatMessageSuccessState(message)
- }
- })
+ fun loadMessages(withCredentials: String, withUrl: String) {
+ val bundle = Bundle()
+ bundle.putString(BundleKeys.KEY_CHAT_URL, withUrl)
+ bundle.putString(BundleKeys.KEY_CREDENTIALS, withCredentials)
+ chatRepository.loadInitialMessages(
+ withNetworkParams = bundle
+ )
}
- fun pullChatMessages(credentials: String, url: String) {
- chatRepository.pullChatMessages(credentials, url, _getFieldMapForChat.value!!)
- .subscribeOn(Schedulers.io())
- .takeUntil { (currentLifeCycleFlag == LifeCycleFlag.PAUSED) }
- ?.observeOn(AndroidSchedulers.mainThread())
- ?.subscribe(object : Observer> {
- override fun onSubscribe(d: Disposable) {
- Log.d(TAG, "pullChatMessages - pullChatMessages SUBSCRIBE")
- disposableSet.add(d)
- }
-
- override fun onError(e: Throwable) {
- Log.e(TAG, "pullChatMessages - pullChatMessages ERROR", e)
- _pullChatMessageViewState.value = PullChatMessageErrorState
- }
-
- override fun onComplete() {
- Log.d(TAG, "pullChatMessages - pullChatMessages COMPLETE")
- _pullChatMessageViewState.value = PullChatMessageCompleteState
- }
-
- override fun onNext(response: Response<*>) {
- val lookIntoFuture = getFieldMapForChat.value?.get("lookIntoFuture") == 1
- _pullChatMessageViewState.value = PullChatMessageSuccessState(response, lookIntoFuture)
- }
- })
- }
+ fun loadMoreMessages(
+ beforeMessageId: Long,
+ roomToken: String,
+ withMessageLimit: Int,
+ withCredentials: String,
+ withUrl: String
+ ) {
+ val bundle = Bundle()
+ bundle.putString(BundleKeys.KEY_CHAT_URL, withUrl)
+ bundle.putString(BundleKeys.KEY_CREDENTIALS, withCredentials)
+ chatRepository.loadMoreMessages(
+ beforeMessageId,
+ roomToken,
+ withMessageLimit,
+ withNetworkParams = bundle
+ )
+ }
+
+ // fun initMessagePolling(withCredentials: String, withUrl: String, roomToken: String) {
+ // val bundle = Bundle()
+ // bundle.putString(BundleKeys.KEY_CHAT_URL, withUrl)
+ // bundle.putString(BundleKeys.KEY_CREDENTIALS, withCredentials)
+ // chatRepository.initMessagePolling(roomToken, withNetworkParams = bundle)
+ // }
fun deleteChatMessages(credentials: String, url: String, messageId: String) {
- chatRepository.deleteChatMessage(credentials, url)
+ chatNetworkDataSource.deleteChatMessage(credentials, url)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer {
@@ -426,7 +438,7 @@ class ChatViewModel @Inject constructor(
}
fun setChatReadMarker(credentials: String, url: String, previousMessageId: Int) {
- chatRepository.setChatReadMarker(credentials, url, previousMessageId)
+ chatNetworkDataSource.setChatReadMarker(credentials, url, previousMessageId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer {
@@ -449,7 +461,7 @@ class ChatViewModel @Inject constructor(
}
fun shareToNotes(credentials: String, url: String, message: String, displayName: String) {
- chatRepository.shareToNotes(credentials, url, message, displayName)
+ chatNetworkDataSource.shareToNotes(credentials, url, message, displayName)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer {
@@ -472,13 +484,13 @@ class ChatViewModel @Inject constructor(
}
fun checkForNoteToSelf(credentials: String, baseUrl: String, includeStatus: Boolean) {
- chatRepository.checkForNoteToSelf(credentials, baseUrl, includeStatus).subscribeOn(Schedulers.io())
+ chatNetworkDataSource.checkForNoteToSelf(credentials, baseUrl, includeStatus).subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(CheckForNoteToSelfObserver())
}
fun shareLocationToNotes(credentials: String, url: String, objectType: String, objectId: String, metadata: String) {
- chatRepository.shareLocationToNotes(credentials, url, objectType, objectId, metadata)
+ chatNetworkDataSource.shareLocationToNotes(credentials, url, objectType, objectId, metadata)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer {
@@ -575,6 +587,7 @@ class ChatViewModel @Inject constructor(
uploadFile(uri.toString(), room, displayName, metaData)
}
}
+
fun stopAndDiscardAudioRecording() {
stopAudioRecording()
Log.d(TAG, "File discarded")
@@ -619,24 +632,38 @@ class ChatViewModel @Inject constructor(
_getCapabilitiesViewState.value = GetCapabilitiesStartState
}
- inner class GetRoomObserver : Observer {
- override fun onSubscribe(d: Disposable) {
- // unused atm
- }
-
- override fun onNext(conversationModel: ConversationModel) {
- _getRoomViewState.value = GetRoomSuccessState(conversationModel)
- }
+ suspend fun getMessageById(url: String, conversationModel: ConversationModel, messageId: Long): Flow =
+ flow {
+ val bundle = Bundle()
+ bundle.putString(BundleKeys.KEY_CHAT_URL, url)
+ bundle.putString(
+ BundleKeys.KEY_CREDENTIALS,
+ userProvider.currentUser.blockingGet().getCredentials()
+ )
+ bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversationModel.token!!)
- override fun onError(e: Throwable) {
- Log.e(TAG, "Error when fetching room")
- _getRoomViewState.value = GetRoomErrorState
+ val message = chatRepository.getMessage(messageId, bundle)
+ emit(message.first())
}
- override fun onComplete() {
- // unused atm
- }
- }
+// 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) {
@@ -704,7 +731,7 @@ class ChatViewModel @Inject constructor(
rooms?.let {
try {
val noteToSelf = rooms.first {
- val model = ConversationModel.mapToConversationModel(it)
+ val model = ConversationModel.mapToConversationModel(it, userProvider.currentUser.blockingGet())
ConversationUtils.isNoteToSelfConversation(model)
}
_getNoteToSelfAvaliability.value = NoteToSelfAvaliableState(noteToSelf.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 509268ac87..4489ca8301 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,12 +14,13 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import com.nextcloud.talk.chat.data.ChatRepository
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.network.ChatNetworkDataSource
import com.nextcloud.talk.models.json.chat.ChatOverallSingleMessage
import com.nextcloud.talk.models.json.generic.GenericOverall
+import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.commons.models.IMessage
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
@@ -28,10 +29,11 @@ import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
class MessageInputViewModel @Inject constructor(
- private val chatRepository: ChatRepository,
+ private val chatNetworkDataSource: ChatNetworkDataSource,
private val audioRecorderManager: AudioRecorderManager,
private val mediaPlayerManager: MediaPlayerManager,
- private val audioFocusRequestManager: AudioFocusRequestManager
+ private val audioFocusRequestManager: AudioFocusRequestManager,
+ private val dataStore: AppPreferences
) : ViewModel(), DefaultLifecycleObserver {
enum class LifeCycleFlag {
PAUSED,
@@ -41,6 +43,16 @@ class MessageInputViewModel @Inject constructor(
lateinit var currentLifeCycleFlag: LifeCycleFlag
val disposableSet = mutableSetOf()
+ data class QueuedMessage(
+ val message: CharSequence? = null,
+ val displayName: String? = null,
+ val replyTo: Int? = null,
+ val sendWithoutNotification: Boolean? = null
+ )
+
+ private var isQueueing: Boolean = false
+ private val messageQueue: MutableList = mutableListOf()
+
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
currentLifeCycleFlag = LifeCycleFlag.RESUMED
@@ -109,6 +121,7 @@ class MessageInputViewModel @Inject constructor(
@Suppress("LongParameterList")
fun sendChatMessage(
+ roomToken: String,
credentials: String,
url: String,
message: CharSequence,
@@ -116,7 +129,13 @@ class MessageInputViewModel @Inject constructor(
replyTo: Int,
sendWithoutNotification: Boolean
) {
- chatRepository.sendChatMessage(
+ if (isQueueing) {
+ messageQueue.add(QueuedMessage(message, displayName, replyTo, sendWithoutNotification))
+ dataStore.saveMessageQueue(roomToken, messageQueue)
+ return
+ }
+
+ chatNetworkDataSource.sendChatMessage(
credentials,
url,
message,
@@ -145,7 +164,7 @@ class MessageInputViewModel @Inject constructor(
}
fun editChatMessage(credentials: String, url: String, text: String) {
- chatRepository.editChatMessage(credentials, url, text)
+ chatNetworkDataSource.editChatMessage(credentials, url, text)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer {
@@ -216,4 +235,28 @@ class MessageInputViewModel @Inject constructor(
fun setRecordingTime(time: Long) {
_getRecordingTime.postValue(time)
}
+
+ fun sendAndEmptyMessageQueue(roomToken: String, credentials: String, url: String) {
+ if (isQueueing) return
+ messageQueue.clear()
+
+ val queue = dataStore.getMessageQueue(roomToken)
+ dataStore.saveMessageQueue(roomToken, null) // empties the queue
+ while (queue.size > 0) {
+ val msg = queue.removeFirst()
+ sendChatMessage(
+ roomToken,
+ credentials,
+ url,
+ msg.message!!,
+ msg.displayName!!,
+ msg.replyTo!!,
+ msg.sendWithoutNotification!!
+ )
+ }
+ }
+
+ fun switchToMessageQueue(shouldQueue: Boolean) {
+ isQueueing = shouldQueue
+ }
}
diff --git a/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivity.kt b/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivity.kt
index ffcb08c4f2..44dc4227c6 100644
--- a/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivity.kt
@@ -46,7 +46,7 @@ import com.nextcloud.talk.jobs.AddParticipantsToConversation
import com.nextcloud.talk.models.RetrofitBucket
import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
-import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter
import com.nextcloud.talk.models.json.participants.Participant
@@ -288,10 +288,10 @@ class ContactsActivity :
// if there are more participants to add, ask for roomName and add them one after another
} else {
- val roomType: Conversation.ConversationType = if (isPublicCall) {
- Conversation.ConversationType.ROOM_PUBLIC_CALL
+ val roomType: ConversationEnums.ConversationType = if (isPublicCall) {
+ ConversationEnums.ConversationType.ROOM_PUBLIC_CALL
} else {
- Conversation.ConversationType.ROOM_GROUP_CALL
+ ConversationEnums.ConversationType.ROOM_GROUP_CALL
}
val userIdsArray = ArrayList(selectedUserIds)
val groupIdsArray = ArrayList(selectedGroupIds)
@@ -338,7 +338,7 @@ class ContactsActivity :
override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle()
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
- bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
+ // bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
@@ -415,7 +415,7 @@ class ContactsActivity :
searchView!!.inputType = InputType.TYPE_TEXT_VARIATION_FILTER
var imeOptions: Int = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_FULLSCREEN
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
- appPreferences?.isKeyboardIncognito == true
+ appPreferences.isKeyboardIncognito == true
) {
imeOptions = imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
}
@@ -804,7 +804,7 @@ class ContactsActivity :
override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle()
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
- bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
+ // bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
diff --git a/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivityCompose.kt b/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivityCompose.kt
index 75fc8c1bdb..ec229ee07e 100644
--- a/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivityCompose.kt
+++ b/app/src/main/java/com/nextcloud/talk/contacts/ContactsActivityCompose.kt
@@ -220,7 +220,7 @@ fun ContactItemRow(contact: AutocompleteUser, contactsViewModel: ContactsViewMod
val conversation = (roomUiState as RoomUiState.Success).conversation
val bundle = Bundle()
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation?.token)
- bundle.putString(BundleKeys.KEY_ROOM_ID, conversation?.roomId)
+ // bundle.putString(BundleKeys.KEY_ROOM_ID, conversation?.roomId)
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
chatIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
diff --git a/app/src/main/java/com/nextcloud/talk/conversation/CreateConversationDialogFragment.kt b/app/src/main/java/com/nextcloud/talk/conversation/CreateConversationDialogFragment.kt
index aa439c11fb..94f12e5b29 100644
--- a/app/src/main/java/com/nextcloud/talk/conversation/CreateConversationDialogFragment.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversation/CreateConversationDialogFragment.kt
@@ -37,7 +37,7 @@ import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.conversation.viewmodel.ConversationViewModel
import com.nextcloud.talk.databinding.DialogCreateConversationBinding
import com.nextcloud.talk.jobs.AddParticipantsToConversation
-import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
@@ -66,7 +66,7 @@ class CreateConversationDialogFragment : DialogFragment() {
private var emojiPopup: EmojiPopup? = null
- private var conversationType: Conversation.ConversationType? = null
+ private var conversationType: ConversationEnums.ConversationType? = null
private var usersToInvite: ArrayList = ArrayList()
private var groupsToInvite: ArrayList = ArrayList()
private var emailsToInvite: ArrayList = ArrayList()
diff --git a/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepository.kt b/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepository.kt
index f85409e1f9..70c50aa644 100644
--- a/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepository.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepository.kt
@@ -6,7 +6,7 @@
*/
package com.nextcloud.talk.conversation.repository
-import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import io.reactivex.Observable
@@ -15,5 +15,8 @@ interface ConversationRepository {
fun renameConversation(roomToken: String, roomNameNew: String): Observable
- fun createConversation(roomName: String, conversationType: Conversation.ConversationType?): Observable
+ fun createConversation(
+ roomName: String,
+ conversationType: ConversationEnums.ConversationType?
+ ): Observable
}
diff --git a/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepositoryImpl.kt
index f18db95788..f5dae7059a 100644
--- a/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepositoryImpl.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversation/repository/ConversationRepositoryImpl.kt
@@ -9,7 +9,7 @@ package com.nextcloud.talk.conversation.repository
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.RetrofitBucket
-import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.utils.ApiUtils
@@ -43,29 +43,30 @@ class ConversationRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
override fun createConversation(
roomName: String,
- conversationType: Conversation.ConversationType?
+ conversationType: ConversationEnums.ConversationType?
): Observable {
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V1))
- val retrofitBucket: RetrofitBucket = if (conversationType == Conversation.ConversationType.ROOM_PUBLIC_CALL) {
- ApiUtils.getRetrofitBucketForCreateRoom(
- apiVersion,
- currentUser.baseUrl!!,
- ROOM_TYPE_PUBLIC,
- null,
- null,
- roomName
- )
- } else {
- ApiUtils.getRetrofitBucketForCreateRoom(
- apiVersion,
- currentUser.baseUrl!!,
- ROOM_TYPE_GROUP,
- null,
- null,
- roomName
- )
- }
+ val retrofitBucket: RetrofitBucket =
+ if (conversationType == ConversationEnums.ConversationType.ROOM_PUBLIC_CALL) {
+ ApiUtils.getRetrofitBucketForCreateRoom(
+ apiVersion,
+ currentUser.baseUrl!!,
+ ROOM_TYPE_PUBLIC,
+ null,
+ null,
+ roomName
+ )
+ } else {
+ ApiUtils.getRetrofitBucketForCreateRoom(
+ apiVersion,
+ currentUser.baseUrl!!,
+ ROOM_TYPE_GROUP,
+ null,
+ null,
+ roomName
+ )
+ }
return ncApi.createRoom(credentials, retrofitBucket.url, retrofitBucket.queryMap)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
diff --git a/app/src/main/java/com/nextcloud/talk/conversation/viewmodel/ConversationViewModel.kt b/app/src/main/java/com/nextcloud/talk/conversation/viewmodel/ConversationViewModel.kt
index cdbe3f0c9e..7783199f1a 100644
--- a/app/src/main/java/com/nextcloud/talk/conversation/viewmodel/ConversationViewModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversation/viewmodel/ConversationViewModel.kt
@@ -10,7 +10,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.nextcloud.talk.conversation.repository.ConversationRepository
-import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.conversations.RoomOverall
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
@@ -40,7 +40,7 @@ class ConversationViewModel @Inject constructor(private val repository: Conversa
disposable?.dispose()
}
- fun createConversation(roomName: String, conversationType: Conversation.ConversationType?) {
+ fun createConversation(roomName: String, conversationType: ConversationEnums.ConversationType?) {
_viewState.value = CreatingState
repository.createConversation(
diff --git a/app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt
index b4ee2aeb0f..8ab356befe 100644
--- a/app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversationinfo/ConversationInfoActivity.kt
@@ -57,11 +57,9 @@ import com.nextcloud.talk.extensions.loadUserAvatar
import com.nextcloud.talk.jobs.DeleteConversationWorker
import com.nextcloud.talk.jobs.LeaveConversationWorker
import com.nextcloud.talk.models.domain.ConversationModel
-import com.nextcloud.talk.models.domain.ConversationType
-import com.nextcloud.talk.models.domain.LobbyState
-import com.nextcloud.talk.models.domain.NotificationLevel
import com.nextcloud.talk.models.domain.converters.DomainEnumNotificationLevelConverter
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.participants.Participant
@@ -350,7 +348,7 @@ class ConversationInfoActivity :
binding.webinarInfoView.webinarSettings.visibility = VISIBLE
val isLobbyOpenToModeratorsOnly =
- conversation!!.lobbyState == LobbyState.LOBBY_STATE_MODERATORS_ONLY
+ conversation!!.lobbyState == ConversationEnums.LobbyState.LOBBY_STATE_MODERATORS_ONLY
binding.webinarInfoView.lobbySwitch.isChecked = isLobbyOpenToModeratorsOnly
reconfigureLobbyTimerView()
@@ -386,8 +384,8 @@ class ConversationInfoActivity :
}
private fun webinaryRoomType(conversation: ConversationModel): Boolean {
- return conversation.type == ConversationType.ROOM_GROUP_CALL ||
- conversation.type == ConversationType.ROOM_PUBLIC_CALL
+ return conversation.type == ConversationEnums.ConversationType.ROOM_GROUP_CALL ||
+ conversation.type == ConversationEnums.ConversationType.ROOM_PUBLIC_CALL
}
private fun reconfigureLobbyTimerView(dateTime: Calendar? = null) {
@@ -402,9 +400,9 @@ class ConversationInfoActivity :
}
conversation!!.lobbyState = if (isChecked) {
- LobbyState.LOBBY_STATE_MODERATORS_ONLY
+ ConversationEnums.LobbyState.LOBBY_STATE_MODERATORS_ONLY
} else {
- LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
+ ConversationEnums.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS
}
if (
@@ -760,13 +758,13 @@ class ConversationInfoActivity :
binding.deleteConversationAction.visibility = VISIBLE
}
- if (ConversationType.ROOM_SYSTEM == conversation!!.type) {
+ if (ConversationEnums.ConversationType.ROOM_SYSTEM == conversation!!.type) {
binding.notificationSettingsView.callNotificationsSwitch.visibility = GONE
}
binding.listBansButton.visibility =
if (ConversationUtils.canModerate(conversationCopy, spreedCapabilities) &&
- ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != conversation!!.type
+ ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != conversation!!.type
) {
VISIBLE
} else {
@@ -922,7 +920,7 @@ class ConversationInfoActivity :
binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.isEnabled = true
binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.alpha = 1.0f
- if (conversation!!.notificationLevel != NotificationLevel.DEFAULT) {
+ if (conversation!!.notificationLevel != ConversationEnums.NotificationLevel.DEFAULT) {
val stringValue: String =
when (
DomainEnumNotificationLevelConverter()
@@ -952,7 +950,7 @@ class ConversationInfoActivity :
}
private fun setProperNotificationValue(conversation: ConversationModel?) {
- if (conversation!!.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
+ if (conversation!!.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL) {
if (CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.MENTION_FLAG)) {
binding.notificationSettingsView.conversationInfoMessageNotificationsDropdown.setText(
resources.getString(R.string.nc_notify_me_always)
@@ -971,7 +969,10 @@ class ConversationInfoActivity :
private fun loadConversationAvatar() {
when (conversation!!.type) {
- ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) {
+ ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(
+ conversation!!.name
+ )
+ ) {
conversation!!.name?.let {
binding.avatarImage.loadUserAvatar(
conversationUser,
@@ -982,7 +983,7 @@ class ConversationInfoActivity :
}
}
- ConversationType.ROOM_GROUP_CALL, ConversationType.ROOM_PUBLIC_CALL -> {
+ ConversationEnums.ConversationType.ROOM_GROUP_CALL, ConversationEnums.ConversationType.ROOM_PUBLIC_CALL -> {
binding.avatarImage.loadConversationAvatar(
conversationUser,
conversation!!,
@@ -991,11 +992,11 @@ class ConversationInfoActivity :
)
}
- ConversationType.ROOM_SYSTEM -> {
+ ConversationEnums.ConversationType.ROOM_SYSTEM -> {
binding.avatarImage.loadSystemAvatar()
}
- ConversationType.NOTE_TO_SELF -> {
+ ConversationEnums.ConversationType.NOTE_TO_SELF -> {
binding.avatarImage.loadNoteToSelfAvatar()
}
diff --git a/app/src/main/java/com/nextcloud/talk/conversationinfo/GuestAccessHelper.kt b/app/src/main/java/com/nextcloud/talk/conversationinfo/GuestAccessHelper.kt
index 9aae36e00d..43b1a88172 100644
--- a/app/src/main/java/com/nextcloud/talk/conversationinfo/GuestAccessHelper.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversationinfo/GuestAccessHelper.kt
@@ -19,8 +19,8 @@ import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
import com.nextcloud.talk.databinding.DialogPasswordBinding
import com.nextcloud.talk.models.domain.ConversationModel
-import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.repositories.conversations.ConversationsRepository
import com.nextcloud.talk.utils.ConversationUtils
import io.reactivex.Observer
@@ -47,7 +47,7 @@ class GuestAccessHelper(
binding.guestAccessView.guestAccessSettings.visibility = View.GONE
}
- if (conversation.type == ConversationType.ROOM_PUBLIC_CALL) {
+ if (conversation.type == ConversationEnums.ConversationType.ROOM_PUBLIC_CALL) {
binding.guestAccessView.allowGuestsSwitch.isChecked = true
showAllOptions()
if (conversation.hasPassword) {
diff --git a/app/src/main/java/com/nextcloud/talk/conversationinfo/viewmodel/ConversationInfoViewModel.kt b/app/src/main/java/com/nextcloud/talk/conversationinfo/viewmodel/ConversationInfoViewModel.kt
index d8b4ec63aa..3a145beebd 100644
--- a/app/src/main/java/com/nextcloud/talk/conversationinfo/viewmodel/ConversationInfoViewModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversationinfo/viewmodel/ConversationInfoViewModel.kt
@@ -12,7 +12,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import com.nextcloud.talk.chat.data.ChatRepository
+import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
@@ -26,7 +26,7 @@ import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
class ConversationInfoViewModel @Inject constructor(
- private val chatRepository: ChatRepository
+ private val chatNetworkDataSource: ChatNetworkDataSource
) : ViewModel() {
object LifeCycleObserver : DefaultLifecycleObserver {
@@ -92,7 +92,7 @@ class ConversationInfoViewModel @Inject constructor(
fun getRoom(user: User, token: String) {
_viewState.value = GetRoomStartState
- chatRepository.getRoom(user, token)
+ chatNetworkDataSource.getRoom(user, token)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(GetRoomObserver())
@@ -104,7 +104,7 @@ class ConversationInfoViewModel @Inject constructor(
if (conversationModel.remoteServer.isNullOrEmpty()) {
_getCapabilitiesViewState.value = GetCapabilitiesSuccessState(user.capabilities!!.spreedCapability!!)
} else {
- chatRepository.getCapabilities(user, token)
+ chatNetworkDataSource.getCapabilities(user, token)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer {
@@ -130,7 +130,7 @@ class ConversationInfoViewModel @Inject constructor(
fun listBans(user: User, token: String) {
val url = ApiUtils.getUrlForBans(user.baseUrl!!, token)
- chatRepository.listBans(user.getCredentials(), url)
+ chatNetworkDataSource.listBans(user.getCredentials(), url)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer> {
@@ -154,7 +154,7 @@ class ConversationInfoViewModel @Inject constructor(
fun banActor(user: User, token: String, actorType: String, actorId: String, internalNote: String) {
val url = ApiUtils.getUrlForBans(user.baseUrl!!, token)
- chatRepository.banActor(user.getCredentials(), url, actorType, actorId, internalNote)
+ chatNetworkDataSource.banActor(user.getCredentials(), url, actorType, actorId, internalNote)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer {
@@ -178,7 +178,7 @@ class ConversationInfoViewModel @Inject constructor(
fun unbanActor(user: User, token: String, banId: Int) {
val url = ApiUtils.getUrlForUnban(user.baseUrl!!, token, banId)
- chatRepository.unbanActor(user.getCredentials(), url)
+ chatNetworkDataSource.unbanActor(user.getCredentials(), url)
.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer {
diff --git a/app/src/main/java/com/nextcloud/talk/conversationinfoedit/ConversationInfoEditActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationinfoedit/ConversationInfoEditActivity.kt
index c030ea7e26..24dba4f13d 100644
--- a/app/src/main/java/com/nextcloud/talk/conversationinfoedit/ConversationInfoEditActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversationinfoedit/ConversationInfoEditActivity.kt
@@ -34,8 +34,8 @@ import com.nextcloud.talk.extensions.loadConversationAvatar
import com.nextcloud.talk.extensions.loadSystemAvatar
import com.nextcloud.talk.extensions.loadUserAvatar
import com.nextcloud.talk.models.domain.ConversationModel
-import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.CapabilitiesUtil
@@ -126,10 +126,6 @@ class ConversationInfoEditActivity : BaseActivity() {
initObservers()
}
- override fun onResume() {
- super.onResume()
- }
-
private fun initObservers() {
conversationInfoEditViewModel.viewState.observe(this) { state ->
when (state) {
@@ -349,15 +345,18 @@ class ConversationInfoEditActivity : BaseActivity() {
setupAvatarOptions()
when (conversation!!.type) {
- ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(conversation!!.name)) {
+ ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL -> if (!TextUtils.isEmpty(
+ conversation!!.name
+ )
+ ) {
conversation!!.name?.let { binding.avatarImage.loadUserAvatar(conversationUser, it, true, false) }
}
- ConversationType.ROOM_GROUP_CALL, ConversationType.ROOM_PUBLIC_CALL -> {
+ ConversationEnums.ConversationType.ROOM_GROUP_CALL, ConversationEnums.ConversationType.ROOM_PUBLIC_CALL -> {
binding.avatarImage.loadConversationAvatar(conversationUser, conversation!!, false, viewThemeUtils)
}
- ConversationType.ROOM_SYSTEM -> {
+ ConversationEnums.ConversationType.ROOM_SYSTEM -> {
binding.avatarImage.loadSystemAvatar()
}
diff --git a/app/src/main/java/com/nextcloud/talk/conversationinfoedit/data/ConversationInfoEditRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/conversationinfoedit/data/ConversationInfoEditRepositoryImpl.kt
index c84e17c659..8ed719a4c7 100644
--- a/app/src/main/java/com/nextcloud/talk/conversationinfoedit/data/ConversationInfoEditRepositoryImpl.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversationinfoedit/data/ConversationInfoEditRepositoryImpl.kt
@@ -31,7 +31,7 @@ class ConversationInfoEditRepositoryImpl(private val ncApi: NcApi, currentUserPr
builder.setType(MultipartBody.FORM)
builder.addFormDataPart(
"file",
- file!!.name,
+ file.name,
file.asRequestBody(Mimetype.IMAGE_PREFIX_GENERIC.toMediaTypeOrNull())
)
val filePart: MultipartBody.Part = MultipartBody.Part.createFormData(
@@ -44,13 +44,13 @@ class ConversationInfoEditRepositoryImpl(private val ncApi: NcApi, currentUserPr
credentials,
ApiUtils.getUrlForConversationAvatar(1, user.baseUrl!!, roomToken),
filePart
- ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
+ ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!, user) }
}
override fun deleteConversationAvatar(user: User, roomToken: String): Observable {
return ncApi.deleteConversationAvatar(
credentials,
ApiUtils.getUrlForConversationAvatar(1, user.baseUrl!!, roomToken)
- ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
+ ).map { ConversationModel.mapToConversationModel(it.ocs?.data!!, user) }
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/conversationinfoedit/viewmodel/ConversationInfoEditViewModel.kt b/app/src/main/java/com/nextcloud/talk/conversationinfoedit/viewmodel/ConversationInfoEditViewModel.kt
index cc9af97fea..ff8af59556 100644
--- a/app/src/main/java/com/nextcloud/talk/conversationinfoedit/viewmodel/ConversationInfoEditViewModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversationinfoedit/viewmodel/ConversationInfoEditViewModel.kt
@@ -10,7 +10,7 @@ import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import com.nextcloud.talk.chat.data.ChatRepository
+import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepository
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
@@ -22,7 +22,7 @@ import java.io.File
import javax.inject.Inject
class ConversationInfoEditViewModel @Inject constructor(
- private val repository: ChatRepository,
+ private val repository: ChatNetworkDataSource,
private val conversationInfoEditRepository: ConversationInfoEditRepository
) : ViewModel() {
diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
index bda2f14ca6..66ec2502f4 100644
--- a/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversationlist/ConversationsListActivity.kt
@@ -45,6 +45,7 @@ import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuItemCompat
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
@@ -80,6 +81,7 @@ import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.contacts.ContactsActivityCompose
import com.nextcloud.talk.conversationlist.viewmodels.ConversationsListViewModel
+import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.ActivityConversationsBinding
import com.nextcloud.talk.events.ConversationsListFetchDataEvent
@@ -91,8 +93,8 @@ import com.nextcloud.talk.jobs.DeleteConversationWorker
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
import com.nextcloud.talk.messagesearch.MessageSearchHelper
import com.nextcloud.talk.messagesearch.MessageSearchHelper.MessageSearchResults
-import com.nextcloud.talk.models.json.conversations.Conversation
-import com.nextcloud.talk.models.json.conversations.RoomsOverall
+import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
import com.nextcloud.talk.settings.SettingsActivity
import com.nextcloud.talk.ui.dialog.ChooseAccountDialogFragment
@@ -105,8 +107,8 @@ import com.nextcloud.talk.utils.BrandingUtils
import com.nextcloud.talk.utils.CapabilitiesUtil.hasSpreedFeatureCapability
import com.nextcloud.talk.utils.CapabilitiesUtil.isServerEOL
import com.nextcloud.talk.utils.CapabilitiesUtil.isUnifiedSearchAvailable
-import com.nextcloud.talk.utils.CapabilitiesUtil.isUserStatusAvailable
import com.nextcloud.talk.utils.ClosedInterfaceImpl
+import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.FileUtils
import com.nextcloud.talk.utils.Mimetype
import com.nextcloud.talk.utils.ParticipantPermissions
@@ -134,6 +136,9 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.BehaviorSubject
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
import org.apache.commons.lang3.builder.CompareToBuilder
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@@ -168,6 +173,9 @@ class ConversationsListActivity :
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
+ @Inject
+ lateinit var networkMonitor: NetworkMonitor
+
lateinit var conversationsListViewModel: ConversationsListViewModel
override val appBarLayoutType: AppBarLayoutType
@@ -190,7 +198,7 @@ class ConversationsListActivity :
private var isRefreshing = false
private var showShareToScreen = false
private var filesToShare: ArrayList? = null
- private var selectedConversation: Conversation? = null
+ private var selectedConversation: ConversationModel? = null
private var textToPaste: String? = ""
private var selectedMessageId: String? = null
private var forwardMessage: Boolean = false
@@ -259,7 +267,7 @@ class ConversationsListActivity :
if (adapter == null) {
adapter = FlexibleAdapter(conversationItems, this, true)
} else {
- binding?.loadingContent?.visibility = View.GONE
+ binding.loadingContent?.visibility = View.GONE
}
adapter!!.addListener(this)
prepareViews()
@@ -295,6 +303,12 @@ class ConversationsListActivity :
}
private fun initObservers() {
+ this.lifecycleScope.launch {
+ networkMonitor.isOnline.onEach { isOnline ->
+ showNetworkErrorDialog(!isOnline)
+ }.collect()
+ }
+
conversationsListViewModel.getFederationInvitationsViewState.observe(this) { state ->
when (state) {
is ConversationsListViewModel.GetFederationInvitationsStartState -> {
@@ -310,7 +324,9 @@ class ConversationsListActivity :
}
is ConversationsListViewModel.GetFederationInvitationsErrorState -> {
- Snackbar.make(binding.root, R.string.get_invitations_error, Snackbar.LENGTH_LONG).show()
+ if (isNetworkAvailable(context)) {
+ Snackbar.make(binding.root, R.string.get_invitations_error, Snackbar.LENGTH_LONG).show()
+ }
}
else -> {}
@@ -334,6 +350,51 @@ class ConversationsListActivity :
else -> {}
}
}
+
+ conversationsListViewModel.getRoomsViewState.observe(this) { state ->
+ when (state) {
+ is ConversationsListViewModel.GetRoomsSuccessState -> {
+ if (adapterWasNull) {
+ adapterWasNull = false
+ binding.loadingContent.visibility = View.GONE
+ }
+ initOverallLayout(state.listIsNotEmpty)
+ binding.swipeRefreshLayoutView.isRefreshing = false
+ }
+
+ is ConversationsListViewModel.GetRoomsErrorState -> {
+ Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_SHORT).show()
+ }
+
+ else -> {}
+ }
+ }
+
+ lifecycleScope.launch {
+ conversationsListViewModel.getRoomsFlow
+ .onEach { list ->
+ // Update Conversations
+ conversationItems.clear()
+ for (conversation in list) {
+ addToConversationItems(conversation)
+ }
+ sortConversations(conversationItems)
+ sortConversations(conversationItemsWithHeader)
+
+ // Filter Conversations
+ if (!filterState.containsValue(true)) filterableConversationItems = conversationItems
+ filterConversation()
+ adapter!!.updateDataSet(filterableConversationItems, false)
+ Handler().postDelayed({ checkToShowUnreadBubble() }, UNREAD_BUBBLE_DELAY.toLong())
+
+ // Fetch Open Conversations
+ val apiVersion = ApiUtils.getConversationApiVersion(
+ currentUser!!,
+ intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1)
+ )
+ fetchOpenConversations(apiVersion)
+ }.collect()
+ }
}
fun filterConversation() {
@@ -374,7 +435,7 @@ class ConversationsListActivity :
updateFilterConversationButtonColor()
}
- private fun filter(conversation: Conversation): Boolean {
+ private fun filter(conversation: ConversationModel): Boolean {
var result = true
for ((k, v) in filterState) {
if (v) {
@@ -383,8 +444,8 @@ class ConversationsListActivity :
(
result &&
(
- conversation.type == Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
- conversation.type == Conversation.ConversationType.FORMER_ONE_TO_ONE
+ conversation.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL ||
+ conversation.type == ConversationEnums.ConversationType.FORMER_ONE_TO_ONE
) &&
(conversation.unreadMessages > 0)
)
@@ -573,7 +634,7 @@ class ConversationsListActivity :
if (!filterState.containsValue(true)) filterableConversationItems = searchableConversationItems
adapter!!.updateDataSet(filterableConversationItems, false)
adapter!!.showAllHeaders()
- binding?.swipeRefreshLayoutView?.isEnabled = false
+ binding.swipeRefreshLayoutView?.isEnabled = false
searchBehaviorSubject.onNext(true)
return true
}
@@ -586,10 +647,10 @@ class ConversationsListActivity :
if (searchHelper != null) {
// cancel any pending searches
searchHelper!!.cancelSearch()
- binding?.swipeRefreshLayoutView?.isRefreshing = false
+ binding.swipeRefreshLayoutView?.isRefreshing = false
searchBehaviorSubject.onNext(false)
}
- binding?.swipeRefreshLayoutView?.isEnabled = true
+ binding.swipeRefreshLayoutView?.isEnabled = true
searchView!!.onActionViewCollapsed()
binding.conversationListAppbar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
@@ -602,7 +663,7 @@ class ConversationsListActivity :
viewThemeUtils.platform.resetStatusBar(this@ConversationsListActivity)
}
- val layoutManager = binding?.recyclerView?.layoutManager as SmoothScrollLinearLayoutManager?
+ val layoutManager = binding.recyclerView?.layoutManager as SmoothScrollLinearLayoutManager?
layoutManager?.scrollToPositionWithOffset(0, 0)
return true
}
@@ -681,71 +742,7 @@ class ConversationsListActivity :
}
fun fetchRooms() {
- val includeStatus = isUserStatusAvailable(userManager.currentUser.blockingGet())
-
- // checks internet connection before fetching rooms
- if (isNetworkAvailable(context)) {
- Log.d(TAG, "Internet connection available")
- dispose(null)
- isRefreshing = true
- conversationItems = ArrayList()
- conversationItemsWithHeader = ArrayList()
- val apiVersion = ApiUtils.getConversationApiVersion(
- currentUser!!,
- intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1)
- )
- val startNanoTime = System.nanoTime()
- Log.d(TAG, "fetchData - getRooms - calling: $startNanoTime")
- roomsQueryDisposable = ncApi.getRooms(
- credentials,
- ApiUtils.getUrlForRooms(
- apiVersion,
- currentUser!!.baseUrl
- ),
- includeStatus
- )
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe({ (ocs): RoomsOverall ->
- Log.d(TAG, "fetchData - getRooms - got response: $startNanoTime")
-
- // This is invoked asynchronously, when server returns a response the view might have been
- // unbound in the meantime. Check if the view is still there.
- // FIXME - does it make sense to update internal data structures even when view has been unbound?
- // if (view == null) {
- // Log.d(TAG, "fetchData - getRooms - view is not bound: $startNanoTime")
- // return@subscribe
- // }
-
- if (adapterWasNull) {
- adapterWasNull = false
- binding?.loadingContent?.visibility = View.GONE
- }
- initOverallLayout(ocs!!.data!!.isNotEmpty())
- for (conversation in ocs.data!!) {
- addToConversationItems(conversation)
- }
- sortConversations(conversationItems)
- sortConversations(conversationItemsWithHeader)
- if (!filterState.containsValue(true)) filterableConversationItems = conversationItems
- filterConversation()
- adapter!!.updateDataSet(filterableConversationItems, false)
- Handler().postDelayed({ checkToShowUnreadBubble() }, UNREAD_BUBBLE_DELAY.toLong())
- fetchOpenConversations(apiVersion)
- binding?.swipeRefreshLayoutView?.isRefreshing = false
- }, { throwable: Throwable ->
- handleHttpExceptions(throwable)
- binding?.swipeRefreshLayoutView?.isRefreshing = false
- dispose(roomsQueryDisposable)
- }) {
- dispose(roomsQueryDisposable)
- binding?.swipeRefreshLayoutView?.isRefreshing = false
- isRefreshing = false
- }
- } else {
- Log.d(TAG, "No internet connection detected")
- showNetworkErrorDialog()
- }
+ conversationsListViewModel.getRooms()
}
private fun fetchPendingInvitations() {
@@ -760,31 +757,31 @@ class ConversationsListActivity :
private fun initOverallLayout(isConversationListNotEmpty: Boolean) {
if (isConversationListNotEmpty) {
- if (binding?.emptyLayout?.visibility != View.GONE) {
- binding?.emptyLayout?.visibility = View.GONE
+ if (binding.emptyLayout?.visibility != View.GONE) {
+ binding.emptyLayout?.visibility = View.GONE
}
- if (binding?.swipeRefreshLayoutView?.visibility != View.VISIBLE) {
- binding?.swipeRefreshLayoutView?.visibility = View.VISIBLE
+ if (binding.swipeRefreshLayoutView?.visibility != View.VISIBLE) {
+ binding.swipeRefreshLayoutView?.visibility = View.VISIBLE
}
} else {
- if (binding?.emptyLayout?.visibility != View.VISIBLE) {
- binding?.emptyLayout?.visibility = View.VISIBLE
+ if (binding.emptyLayout?.visibility != View.VISIBLE) {
+ binding.emptyLayout?.visibility = View.VISIBLE
}
- if (binding?.swipeRefreshLayoutView?.visibility != View.GONE) {
- binding?.swipeRefreshLayoutView?.visibility = View.GONE
+ if (binding.swipeRefreshLayoutView?.visibility != View.GONE) {
+ binding.swipeRefreshLayoutView?.visibility = View.GONE
}
}
}
- private fun addToConversationItems(conversation: Conversation) {
+ private fun addToConversationItems(conversation: ConversationModel) {
if (intent.getStringExtra(KEY_FORWARD_HIDE_SOURCE_ROOM) != null &&
- intent.getStringExtra(KEY_FORWARD_HIDE_SOURCE_ROOM) == conversation.roomId
+ intent.getStringExtra(KEY_FORWARD_HIDE_SOURCE_ROOM) == conversation.token
) {
return
}
- if (conversation.objectType == Conversation.ObjectType.ROOM &&
- conversation.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY
+ if (conversation.objectType == ConversationEnums.ObjectType.ROOM &&
+ conversation.lobbyState == ConversationEnums.LobbyState.LOBBY_STATE_MODERATORS_ONLY
) {
return
}
@@ -851,31 +848,8 @@ class ConversationsListActivity :
}
}
- private fun showNetworkErrorDialog() {
- binding.floatingActionButton.let {
- val dialogBuilder = MaterialAlertDialogBuilder(it.context)
- .setIcon(
- viewThemeUtils.dialog.colorMaterialAlertDialogIcon(
- context,
- R.drawable.ic_baseline_error_outline_24dp
- )
- )
- .setTitle(R.string.nc_check_your_internet)
- .setCancelable(false)
- .setNegativeButton(R.string.close, null)
- .setNeutralButton(R.string.nc_refresh) { _, _ ->
- fetchRooms()
- fetchPendingInvitations()
- }
-
- viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
- val dialog = dialogBuilder.show()
- viewThemeUtils.platform.colorTextButtons(
- dialog.getButton(AlertDialog.BUTTON_POSITIVE),
- dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
- dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
- )
- }
+ private fun showNetworkErrorDialog(show: Boolean) {
+ binding.chatListConnectionLost.visibility = if (show) View.VISIBLE else View.GONE
}
@Suppress("ReturnCount")
@@ -909,35 +883,35 @@ class ConversationsListActivity :
)
) {
val openConversationItems: MutableList> = ArrayList()
- openConversationsQueryDisposable = ncApi.getOpenConversations(
- credentials,
- ApiUtils.getUrlForOpenConversations(apiVersion, currentUser!!.baseUrl!!)
- )
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe({ (ocs): RoomsOverall ->
- for (conversation in ocs!!.data!!) {
- val headerTitle = resources!!.getString(R.string.openConversations)
- var genericTextHeaderItem: GenericTextHeaderItem
- if (!callHeaderItems.containsKey(headerTitle)) {
- genericTextHeaderItem = GenericTextHeaderItem(headerTitle, viewThemeUtils)
- callHeaderItems[headerTitle] = genericTextHeaderItem
- }
- val conversationItem = ConversationItem(
- conversation,
- currentUser!!,
- this,
- callHeaderItems[headerTitle],
- viewThemeUtils
- )
- openConversationItems.add(conversationItem)
- }
- searchableConversationItems.addAll(openConversationItems)
- }, { throwable: Throwable ->
- Log.e(TAG, "fetchData - getRooms - ERROR", throwable)
- handleHttpExceptions(throwable)
- dispose(openConversationsQueryDisposable)
- }) { dispose(openConversationsQueryDisposable) }
+ // openConversationsQueryDisposable = ncApi.getOpenConversations(
+ // credentials,
+ // ApiUtils.getUrlForOpenConversations(apiVersion, currentUser!!.baseUrl!!)
+ // )
+ // .subscribeOn(Schedulers.io())
+ // .observeOn(AndroidSchedulers.mainThread())
+ // .subscribe({ (ocs): RoomsOverall ->
+ // for (conversation in ocs!!.data!!) {
+ // val headerTitle = resources!!.getString(R.string.openConversations)
+ // var genericTextHeaderItem: GenericTextHeaderItem
+ // if (!callHeaderItems.containsKey(headerTitle)) {
+ // genericTextHeaderItem = GenericTextHeaderItem(headerTitle, viewThemeUtils)
+ // callHeaderItems[headerTitle] = genericTextHeaderItem
+ // }
+ // val conversationItem = ConversationItem(
+ // conversation,
+ // currentUser!!,
+ // this,
+ // callHeaderItems[headerTitle],
+ // viewThemeUtils
+ // )
+ // openConversationItems.add(conversationItem)
+ // }
+ // searchableConversationItems.addAll(openConversationItems)
+ // }, { throwable: Throwable ->
+ // Log.e(TAG, "fetchData - getRooms - ERROR", throwable)
+ // handleHttpExceptions(throwable)
+ // dispose(openConversationsQueryDisposable)
+ // }) { dispose(openConversationsQueryDisposable) }
} else {
Log.d(TAG, "no open conversations fetched because of missing capability")
}
@@ -979,24 +953,24 @@ class ConversationsListActivity :
}
}
})
- binding?.recyclerView?.setOnTouchListener { v: View, _: MotionEvent? ->
+ binding.recyclerView?.setOnTouchListener { v: View, _: MotionEvent? ->
if (!isDestroyed) {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(v.windowToken, 0)
}
false
}
- binding?.swipeRefreshLayoutView?.setOnRefreshListener {
+ binding.swipeRefreshLayoutView?.setOnRefreshListener {
fetchRooms()
fetchPendingInvitations()
}
- binding?.swipeRefreshLayoutView?.let { viewThemeUtils.androidx.themeSwipeRefreshLayout(it) }
- binding?.emptyLayout?.setOnClickListener { showNewConversationsScreen() }
- binding?.floatingActionButton?.setOnClickListener {
+ binding.swipeRefreshLayoutView?.let { viewThemeUtils.androidx.themeSwipeRefreshLayout(it) }
+ binding.emptyLayout?.setOnClickListener { showNewConversationsScreen() }
+ binding.floatingActionButton?.setOnClickListener {
run(context)
showNewConversationsScreen()
}
- binding?.floatingActionButton?.let { viewThemeUtils.material.themeFAB(it) }
+ binding.floatingActionButton?.let { viewThemeUtils.material.themeFAB(it) }
binding.switchAccountButton.setOnClickListener {
if (resources != null && resources!!.getBoolean(R.bool.multiaccount_support)) {
@@ -1015,13 +989,13 @@ class ConversationsListActivity :
newFragment.show(supportFragmentManager, FilterConversationFragment.TAG)
}
- binding?.newMentionPopupBubble?.hide()
- binding?.newMentionPopupBubble?.setPopupBubbleListener {
- binding?.recyclerView?.smoothScrollToPosition(
+ binding.newMentionPopupBubble?.hide()
+ binding.newMentionPopupBubble?.setPopupBubbleListener {
+ binding.recyclerView?.smoothScrollToPosition(
nextUnreadConversationScrollPosition
)
}
- binding?.newMentionPopupBubble?.let { viewThemeUtils.material.colorMaterialButtonPrimaryFilled(it) }
+ binding.newMentionPopupBubble?.let { viewThemeUtils.material.colorMaterialButtonPrimaryFilled(it) }
}
private fun hideLogoForBrandedClients() {
@@ -1041,17 +1015,17 @@ class ConversationsListActivity :
try {
val lastVisibleItem = layoutManager!!.findLastCompletelyVisibleItemPosition()
for (flexItem in conversationItems) {
- val conversation: Conversation = (flexItem as ConversationItem).model
+ val conversation: ConversationModel = (flexItem as ConversationItem).model
val position = adapter!!.getGlobalPositionOf(flexItem)
if (hasUnreadItems(conversation) && position > lastVisibleItem) {
nextUnreadConversationScrollPosition = position
- if (!binding?.newMentionPopupBubble?.isShown!!) {
- binding?.newMentionPopupBubble?.show()
+ if (!binding.newMentionPopupBubble?.isShown!!) {
+ binding.newMentionPopupBubble?.show()
}
return@subscribe
}
nextUnreadConversationScrollPosition = 0
- binding?.newMentionPopupBubble?.hide()
+ binding.newMentionPopupBubble?.hide()
}
} catch (e: NullPointerException) {
Log.d(
@@ -1066,10 +1040,10 @@ class ConversationsListActivity :
}
}
- private fun hasUnreadItems(conversation: Conversation) =
+ private fun hasUnreadItems(conversation: ConversationModel) =
conversation.unreadMention ||
conversation.unreadMessages > 0 &&
- conversation.type === Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+ conversation.type === ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
private fun showNewConversationsScreen() {
val intent = Intent(context, ContactsActivityCompose::class.java)
@@ -1157,7 +1131,7 @@ class ConversationsListActivity :
@SuppressLint("CheckResult") // handled by helper
private fun startMessageSearch(search: String?) {
- binding?.swipeRefreshLayoutView?.isRefreshing = true
+ binding.swipeRefreshLayoutView?.isRefreshing = true
searchHelper?.startMessageSearch(search!!)
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
@@ -1214,7 +1188,7 @@ class ConversationsListActivity :
}
@Suppress("Detekt.ComplexMethod")
- private fun handleConversation(conversation: Conversation?) {
+ private fun handleConversation(conversation: ConversationModel?) {
selectedConversation = conversation
if (selectedConversation != null) {
val hasChatPermission = ParticipantPermissions(
@@ -1244,19 +1218,19 @@ class ConversationsListActivity :
}
}
- private fun shouldShowLobby(conversation: Conversation): Boolean {
+ private fun shouldShowLobby(conversation: ConversationModel): Boolean {
val participantPermissions = ParticipantPermissions(
currentUser!!.capabilities?.spreedCapability!!,
- conversation
+ selectedConversation!!
)
- return conversation.lobbyState == Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
- !conversation.canModerate(currentUser!!) &&
+ return conversation.lobbyState == ConversationEnums.LobbyState.LOBBY_STATE_MODERATORS_ONLY &&
+ !ConversationUtils.canModerate(conversation, currentUser!!.capabilities!!.spreedCapability!!) &&
!participantPermissions.canIgnoreLobby()
}
- private fun isReadOnlyConversation(conversation: Conversation): Boolean {
+ private fun isReadOnlyConversation(conversation: ConversationModel): Boolean {
return conversation.conversationReadOnlyState ===
- Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY
+ ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_ONLY
}
private fun handleSharedData() {
@@ -1482,7 +1456,7 @@ class ConversationsListActivity :
val bundle = Bundle()
bundle.putString(KEY_ROOM_TOKEN, selectedConversation!!.token)
- bundle.putString(KEY_ROOM_ID, selectedConversation!!.roomId)
+ // bundle.putString(KEY_ROOM_ID, selectedConversation!!.roomId)
bundle.putString(KEY_SHARED_TEXT, textToPaste)
if (selectedMessageId != null) {
bundle.putString(BundleKeys.KEY_MESSAGE_ID, selectedMessageId)
@@ -1519,7 +1493,7 @@ class ConversationsListActivity :
}, BOTTOM_SHEET_DELAY)
}
- fun showDeleteConversationDialog(conversation: Conversation) {
+ fun showDeleteConversationDialog(conversation: ConversationModel) {
binding.floatingActionButton.let {
val dialogBuilder = MaterialAlertDialogBuilder(it.context)
.setIcon(
@@ -1751,7 +1725,7 @@ class ConversationsListActivity :
}
}
- private fun deleteConversation(conversation: Conversation) {
+ private fun deleteConversation(conversation: ConversationModel) {
val data = Data.Builder()
data.putLong(
KEY_INTERNAL_USER_ID,
@@ -1810,15 +1784,15 @@ class ConversationsListActivity :
}
// add unified search result at the end of the list
adapter!!.addItems(adapter!!.mainItemCount + adapter!!.scrollableHeaders.size, adapterItems)
- binding?.recyclerView?.scrollToPosition(0)
+ binding.recyclerView?.scrollToPosition(0)
}
}
- binding?.swipeRefreshLayoutView?.isRefreshing = false
+ binding.swipeRefreshLayoutView?.isRefreshing = false
}
private fun onMessageSearchError(throwable: Throwable) {
handleHttpExceptions(throwable)
- binding?.swipeRefreshLayoutView?.isRefreshing = false
+ binding.swipeRefreshLayoutView?.isRefreshing = false
showErrorDialog()
}
diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/data/ConversationsListRepository.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/data/ConversationsListRepository.kt
deleted file mode 100644
index 6f7baf1a58..0000000000
--- a/app/src/main/java/com/nextcloud/talk/conversationlist/data/ConversationsListRepository.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- * Nextcloud Talk - Android Client
- *
- * SPDX-FileCopyrightText: 2023 Marcel Hibbe
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package com.nextcloud.talk.conversationlist.data
-
-interface ConversationsListRepository
diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/data/ConversationsListRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/data/ConversationsListRepositoryImpl.kt
deleted file mode 100644
index 098b27e71c..0000000000
--- a/app/src/main/java/com/nextcloud/talk/conversationlist/data/ConversationsListRepositoryImpl.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Nextcloud Talk - Android Client
- *
- * SPDX-FileCopyrightText: 2023 Marcel Hibbe
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package com.nextcloud.talk.conversationlist.data
-
-import com.nextcloud.talk.api.NcApi
-
-class ConversationsListRepositoryImpl(private val ncApi: NcApi) : ConversationsListRepository
diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/data/OfflineConversationsRepository.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/data/OfflineConversationsRepository.kt
new file mode 100644
index 0000000000..a264a84eee
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/conversationlist/data/OfflineConversationsRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.conversationlist.data
+
+import com.nextcloud.talk.models.domain.ConversationModel
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+
+interface OfflineConversationsRepository {
+
+ /**
+ * Stream of a list of rooms, for use in the conversation list.
+ */
+ val roomListFlow: Flow>
+
+ /**
+ * Stream of a single conversation, for use in each conversations settings.
+ */
+ val conversationFlow: Flow
+
+ /**
+ * Loads rooms from local storage. If the rooms are not found, then it
+ * synchronizes the database with the server, before retrying exactly once. Only
+ * emits to [roomListFlow] if the rooms list is not empty.
+ *
+ */
+ fun getRooms(): Job
+
+ /**
+ * Called once onStart to emit a conversation to [conversationFlow]
+ * to be handled asynchronously.
+ */
+ fun getConversationSettings(roomToken: String): Job
+}
diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/ConversationsNetworkDataSource.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/ConversationsNetworkDataSource.kt
new file mode 100644
index 0000000000..bf3ae39d12
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/ConversationsNetworkDataSource.kt
@@ -0,0 +1,16 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.conversationlist.data.network
+
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.json.conversations.Conversation
+import io.reactivex.Observable
+
+interface ConversationsNetworkDataSource {
+ fun getRooms(user: User, url: String, includeStatus: Boolean): Observable>
+}
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
new file mode 100644
index 0000000000..45482ba64f
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/OfflineFirstConversationsRepository.kt
@@ -0,0 +1,118 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Julius Linus
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.conversationlist.data.network
+
+import android.util.Log
+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
+import com.nextcloud.talk.data.database.mappers.asModel
+import com.nextcloud.talk.data.database.model.ConversationEntity
+import com.nextcloud.talk.data.network.NetworkMonitor
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+class OfflineFirstConversationsRepository @Inject constructor(
+ private val dao: ConversationsDao,
+ private val network: ConversationsNetworkDataSource,
+ private val monitor: NetworkMonitor,
+ private val currentUserProviderNew: CurrentUserProviderNew
+) : OfflineConversationsRepository {
+ override val roomListFlow: Flow>
+ get() = _roomListFlow
+ private val _roomListFlow: MutableSharedFlow> = MutableSharedFlow()
+
+ override val conversationFlow: Flow
+ get() = _conversationFlow
+ private val _conversationFlow: MutableSharedFlow = MutableSharedFlow()
+
+ private val scope = CoroutineScope(Dispatchers.IO)
+ private var user: User = currentUserProviderNew.currentUser.blockingGet()
+
+ override fun getRooms(): Job =
+ scope.launch {
+ val resultsFromSync = sync()
+ if (!resultsFromSync.isNullOrEmpty()) {
+ val conversations = resultsFromSync.map(ConversationEntity::asModel)
+ _roomListFlow.emit(conversations)
+ } else {
+ val conversationsFromDb = getListOfConversations(user.id!!)
+ _roomListFlow.emit(conversationsFromDb)
+ }
+ }
+
+ override fun getConversationSettings(roomToken: String): Job =
+ scope.launch {
+ val id = user.id!!
+ val model = getConversation(id, roomToken)
+ model.let { _conversationFlow.emit(model) }
+ }
+
+ private suspend fun sync(): List? {
+ var conversationsFromSync: List? = null
+
+ if (!monitor.isOnline.first()) {
+ Log.d(OfflineFirstChatRepository.TAG, "Device is offline, can't load conversations from server")
+ return null
+ }
+
+ try {
+ val conversationsList = network.getRooms(user, user.baseUrl!!, false)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .blockingSingle()
+
+ conversationsFromSync = conversationsList.map {
+ it.asEntity(user.id!!)
+ }
+
+ deleteLeftConversations(conversationsFromSync)
+ dao.upsertConversations(conversationsFromSync)
+ } catch (e: Exception) {
+ Log.e(TAG, "Something went wrong when fetching conversations", e)
+ }
+ return conversationsFromSync
+ }
+
+ private suspend fun deleteLeftConversations(conversationsFromSync: List) {
+ val oldConversationsFromDb = dao.getConversationsForUser(user.id!!).first()
+
+ val conversationsToDelete = oldConversationsFromDb.filterNot { conversationsFromSync.contains(it) }
+ val conversationIdsToDelete = conversationsToDelete.map { it.internalId }
+
+ dao.deleteConversations(conversationIdsToDelete)
+ }
+
+ private suspend fun getListOfConversations(accountId: Long): List =
+ dao.getConversationsForUser(accountId).map {
+ it.map(ConversationEntity::asModel)
+ }.first()
+
+ private suspend fun getConversation(accountId: Long, token: String): ConversationModel {
+ val entity = dao.getConversationForUser(accountId, token).first()
+ return entity.asModel()
+ }
+
+ companion object {
+ val TAG = OfflineFirstConversationsRepository::class.simpleName
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/RetrofitConversationsNetwork.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/RetrofitConversationsNetwork.kt
new file mode 100644
index 0000000000..9669688625
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/conversationlist/data/network/RetrofitConversationsNetwork.kt
@@ -0,0 +1,28 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2023 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package com.nextcloud.talk.conversationlist.data.network
+
+import com.nextcloud.talk.api.NcApi
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.utils.ApiUtils
+import io.reactivex.Observable
+
+class RetrofitConversationsNetwork(private val ncApi: NcApi) : ConversationsNetworkDataSource {
+ override fun getRooms(user: User, url: String, includeStatus: Boolean): Observable> {
+ val credentials: String = ApiUtils.getCredentials(user.username, user.token)!!
+ val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
+
+ return ncApi.getRooms(
+ credentials,
+ ApiUtils.getUrlForRooms(apiVersion, user.baseUrl!!),
+ includeStatus
+ ).map { it ->
+ it.ocs?.data?.map { it } ?: listOf()
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt b/app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt
index 50dc68386e..f70cf134a0 100644
--- a/app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/conversationlist/viewmodels/ConversationsListViewModel.kt
@@ -10,7 +10,7 @@ import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
-import com.nextcloud.talk.conversationlist.data.ConversationsListRepository
+import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
import com.nextcloud.talk.invitation.data.InvitationsModel
import com.nextcloud.talk.invitation.data.InvitationsRepository
import com.nextcloud.talk.users.UserManager
@@ -18,21 +18,36 @@ import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
class ConversationsListViewModel @Inject constructor(
- private val conversationsListRepository: ConversationsListRepository
+ private val repository: OfflineConversationsRepository,
+ var userManager: UserManager
) :
ViewModel() {
@Inject
lateinit var invitationsRepository: InvitationsRepository
- @Inject
- lateinit var userManager: UserManager
-
sealed interface ViewState
+ object GetRoomsStartState : ViewState
+ object GetRoomsErrorState : ViewState
+ open class GetRoomsSuccessState(val listIsNotEmpty: Boolean) : ViewState
+
+ private val _getRoomsViewState: MutableLiveData = MutableLiveData(GetRoomsStartState)
+ val getRoomsViewState: LiveData
+ get() = _getRoomsViewState
+
+ val getRoomsFlow = repository.roomListFlow
+ .onEach { list ->
+ _getRoomsViewState.value = GetRoomsSuccessState(list.isNotEmpty())
+ }.catch {
+ _getRoomsViewState.value = GetRoomsErrorState
+ }
+
object GetFederationInvitationsStartState : ViewState
object GetFederationInvitationsErrorState : ViewState
@@ -63,6 +78,12 @@ class ConversationsListViewModel @Inject constructor(
}
}
+ fun getRooms() {
+ val startNanoTime = System.nanoTime()
+ Log.d(TAG, "fetchData - getRooms - calling: $startNanoTime")
+ repository.getRooms()
+ }
+
inner class FederatedInvitationsObserver : Observer {
override fun onSubscribe(d: Disposable) {
// unused atm
diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/DaosModule.kt b/app/src/main/java/com/nextcloud/talk/dagger/modules/DaosModule.kt
new file mode 100644
index 0000000000..27ec540a07
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/DaosModule.kt
@@ -0,0 +1,27 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.dagger.modules
+
+import com.nextcloud.talk.data.database.dao.ChatBlocksDao
+import com.nextcloud.talk.data.database.dao.ChatMessagesDao
+import com.nextcloud.talk.data.database.dao.ConversationsDao
+import com.nextcloud.talk.data.source.local.TalkDatabase
+import dagger.Module
+import dagger.Provides
+
+@Module
+internal object DaosModule {
+ @Provides
+ fun providesConversationsDao(database: TalkDatabase): ConversationsDao = database.conversationsDao()
+
+ @Provides
+ fun providesChatDao(database: TalkDatabase): ChatMessagesDao = database.chatMessagesDao()
+
+ @Provides
+ fun providesChatBlocksDao(database: TalkDatabase): ChatBlocksDao = database.chatBlocksDao()
+}
diff --git a/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java b/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java
index ed2066f9bd..253e0c3ce7 100644
--- a/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java
+++ b/app/src/main/java/com/nextcloud/talk/dagger/modules/DatabaseModule.java
@@ -9,6 +9,8 @@
import android.content.Context;
+import com.nextcloud.talk.data.network.NetworkMonitor;
+import com.nextcloud.talk.data.network.NetworkMonitorImpl;
import com.nextcloud.talk.data.source.local.TalkDatabase;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.nextcloud.talk.utils.preferences.AppPreferencesImpl;
@@ -44,4 +46,10 @@ public TalkDatabase provideTalkDatabase(@NonNull final Context context,
@NonNull final AppPreferences appPreferences) {
return TalkDatabase.getInstance(context, appPreferences);
}
+
+ @Provides
+ @Singleton
+ public NetworkMonitor provideNetworkMonitor(@NonNull final Context poContext) {
+ return new NetworkMonitorImpl(poContext);
+ }
}
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 4c882dba2a..666ab69d02 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
@@ -1,7 +1,7 @@
/*
* Nextcloud Talk - Android Client
*
- * SPDX-FileCopyrightText: 2022 Marcel Hibbe
+ * SPDX-FileCopyrightText: 2022-2024 Marcel Hibbe
* SPDX-FileCopyrightText: 2022 Andy Scherzinger
* SPDX-FileCopyrightText: 2022 Álvaro Brey
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
@@ -10,17 +10,25 @@
package com.nextcloud.talk.dagger.modules
import com.nextcloud.talk.api.NcApi
+import com.nextcloud.talk.chat.data.ChatMessageRepository
+import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
+import com.nextcloud.talk.chat.data.network.OfflineFirstChatRepository
+import com.nextcloud.talk.chat.data.network.RetrofitChatNetwork
import com.nextcloud.talk.api.NcApiCoroutines
-import com.nextcloud.talk.chat.data.ChatRepository
-import com.nextcloud.talk.chat.data.network.NetworkChatRepositoryImpl
import com.nextcloud.talk.contacts.ContactsRepository
import com.nextcloud.talk.contacts.ContactsRepositoryImpl
import com.nextcloud.talk.conversation.repository.ConversationRepository
import com.nextcloud.talk.conversation.repository.ConversationRepositoryImpl
import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepository
import com.nextcloud.talk.conversationinfoedit.data.ConversationInfoEditRepositoryImpl
-import com.nextcloud.talk.conversationlist.data.ConversationsListRepository
-import com.nextcloud.talk.conversationlist.data.ConversationsListRepositoryImpl
+import com.nextcloud.talk.conversationlist.data.OfflineConversationsRepository
+import com.nextcloud.talk.conversationlist.data.network.ConversationsNetworkDataSource
+import com.nextcloud.talk.conversationlist.data.network.OfflineFirstConversationsRepository
+import com.nextcloud.talk.conversationlist.data.network.RetrofitConversationsNetwork
+import com.nextcloud.talk.data.database.dao.ChatBlocksDao
+import com.nextcloud.talk.data.database.dao.ChatMessagesDao
+import com.nextcloud.talk.data.database.dao.ConversationsDao
+import com.nextcloud.talk.data.network.NetworkMonitor
import com.nextcloud.talk.data.source.local.TalkDatabase
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepository
import com.nextcloud.talk.data.storage.ArbitraryStoragesRepositoryImpl
@@ -51,6 +59,7 @@ 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
@@ -97,8 +106,12 @@ class RepositoryModule {
}
@Provides
- fun provideReactionsRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): ReactionsRepository {
- return ReactionsRepositoryImpl(ncApi, userProvider)
+ fun provideReactionsRepository(
+ ncApi: NcApi,
+ userProvider: CurrentUserProviderNew,
+ dao: ChatMessagesDao
+ ): ReactionsRepository {
+ return ReactionsRepositoryImpl(ncApi, userProvider, dao)
}
@Provides
@@ -128,13 +141,13 @@ class RepositoryModule {
}
@Provides
- fun provideConversationsListRepository(ncApi: NcApi): ConversationsListRepository {
- return ConversationsListRepositoryImpl(ncApi)
+ fun provideChatNetworkDataSource(ncApi: NcApi): ChatNetworkDataSource {
+ return RetrofitChatNetwork(ncApi)
}
@Provides
- fun provideChatRepository(ncApi: NcApi): ChatRepository {
- return NetworkChatRepositoryImpl(ncApi)
+ fun provideConversationsNetworkDataSource(ncApi: NcApi): ConversationsNetworkDataSource {
+ return RetrofitConversationsNetwork(ncApi)
}
@Provides
@@ -155,6 +168,35 @@ class RepositoryModule {
return InvitationsRepositoryImpl(ncApi)
}
+ @Provides
+ fun provideOfflineFirstChatRepository(
+ chatMessagesDao: ChatMessagesDao,
+ chatBlocksDao: ChatBlocksDao,
+ dataSource: ChatNetworkDataSource,
+ appPreferences: AppPreferences,
+ networkMonitor: NetworkMonitor,
+ userProvider: CurrentUserProviderNew
+ ): ChatMessageRepository {
+ return OfflineFirstChatRepository(
+ chatMessagesDao,
+ chatBlocksDao,
+ dataSource,
+ appPreferences,
+ networkMonitor,
+ userProvider
+ )
+ }
+
+ @Provides
+ fun provideOfflineFirstConversationsRepository(
+ dao: ConversationsDao,
+ dataSource: ConversationsNetworkDataSource,
+ networkMonitor: NetworkMonitor,
+ currentUserProviderNew: CurrentUserProviderNew
+ ): OfflineConversationsRepository {
+ return OfflineFirstConversationsRepository(dao, dataSource, networkMonitor, currentUserProviderNew)
+ }
+
@Provides
fun provideContactsRepository(ncApiCoroutines: NcApiCoroutines, userManager: UserManager): ContactsRepository {
return ContactsRepositoryImpl(ncApiCoroutines, userManager)
diff --git a/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatBlocksDao.kt b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatBlocksDao.kt
new file mode 100644
index 0000000000..171feb228f
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatBlocksDao.kt
@@ -0,0 +1,101 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.database.dao
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.nextcloud.talk.data.database.model.ChatBlockEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface ChatBlocksDao {
+ @Delete
+ fun deleteChatBlocks(blocks: List)
+
+ @Query(
+ """
+ SELECT *
+ FROM ChatBlocks
+ WHERE internalConversationId in (:internalConversationId)
+ ORDER BY newestMessageId ASC
+ """
+ )
+ fun getChatBlocks(internalConversationId: String): Flow>
+
+ // @Query(
+ // """
+ // SELECT *
+ // FROM ChatBlocks
+ // WHERE internalConversationId in (:internalConversationId)
+ // AND newestMessageId >= :messageId
+ // ORDER BY newestMessageId ASC
+ // """
+ // )
+ // fun getChatBlocksThatReachMessageId(
+ // internalConversationId: String,
+ // messageId: Long
+ // ):
+ // Flow>
+
+ @Query(
+ """
+ SELECT *
+ FROM ChatBlocks
+ WHERE internalConversationId in (:internalConversationId)
+ AND oldestMessageId <= :messageId
+ AND newestMessageId >= :messageId
+ ORDER BY newestMessageId ASC
+ """
+ )
+ fun getChatBlocksContainingMessageId(internalConversationId: String, messageId: Long): Flow>
+
+
+ @Query(
+ """
+ SELECT *
+ FROM ChatBlocks
+ WHERE internalConversationId = :internalConversationId
+ AND(
+ (oldestMessageId <= :oldestMessageId AND newestMessageId >= :oldestMessageId)
+ OR
+ (oldestMessageId <= :newestMessageId AND newestMessageId >= :newestMessageId)
+ OR
+ (oldestMessageId >= :oldestMessageId AND newestMessageId <= :newestMessageId)
+ )
+ ORDER BY newestMessageId ASC
+ """
+ )
+ fun getConnectedChatBlocks(
+ internalConversationId: String,
+ oldestMessageId: Long,
+ newestMessageId: Long
+ ): Flow>
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun upsertChatBlock(chatBlock: ChatBlockEntity)
+
+ @Query(
+ """
+ DELETE FROM ChatBlocks
+ WHERE internalConversationId LIKE :pattern
+ """
+ )
+ fun clearChatBlocksForUser(pattern: String)
+
+ @Query(
+ """
+ DELETE FROM ChatBlocks
+ WHERE internalConversationId = :internalConversationId
+ AND oldestMessageId < :messageId
+ """
+ )
+ fun deleteChatBlocksOlderThan(internalConversationId: String, messageId: Long)
+}
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
new file mode 100644
index 0000000000..6fbf61ca1d
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/database/dao/ChatMessagesDao.kt
@@ -0,0 +1,143 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.database.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import androidx.room.Update
+import com.nextcloud.talk.data.database.model.ChatMessageEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface ChatMessagesDao {
+ @Query(
+ """
+ SELECT MAX(id) as max_items
+ FROM ChatMessages
+ WHERE internalConversationId = :internalConversationId
+ """
+ )
+ fun getNewestMessageId(internalConversationId: String): Long
+
+ @Query(
+ """
+ SELECT *
+ FROM ChatMessages
+ WHERE internalConversationId = :internalConversationId
+ ORDER BY timestamp DESC, id DESC
+ """
+ )
+ fun getMessagesForConversation(internalConversationId: String): Flow>
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun upsertChatMessages(chatMessages: List)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun upsertChatMessage(chatMessage: ChatMessageEntity)
+
+ @Query(
+ """
+ SELECT *
+ FROM ChatMessages
+ WHERE internalConversationId = :internalConversationId AND id = :messageId
+ """
+ )
+ fun getChatMessageForConversation(internalConversationId: String, messageId: Long): Flow
+
+ @Query(
+ value = """
+ DELETE FROM ChatMessages
+ WHERE id in (:messageIds)
+ """
+ )
+ fun deleteChatMessages(messageIds: List)
+
+ @Update
+ fun updateChatMessage(message: ChatMessageEntity)
+
+ @Query(
+ """
+ SELECT *
+ FROM ChatMessages
+ WHERE id in (:messageIds)
+ ORDER BY timestamp ASC, id ASC
+ """
+ )
+ fun getMessagesFromIds(messageIds: List): Flow>
+
+ @Query(
+ """
+ SELECT *
+ FROM ChatMessages
+ WHERE internalConversationId = :internalConversationId AND id >= :messageId
+ ORDER BY timestamp ASC, id ASC
+ """
+ )
+ fun getMessagesForConversationSince(internalConversationId: String, messageId: Long): Flow>
+
+ @Query(
+ """
+ SELECT *
+ FROM ChatMessages
+ WHERE internalConversationId = :internalConversationId
+ AND id < :messageId
+ ORDER BY timestamp DESC, id DESC
+ LIMIT :limit
+ """
+ )
+ fun getMessagesForConversationBefore(
+ internalConversationId: String,
+ messageId: Long,
+ limit: Int
+ ): Flow>
+
+ @Query(
+ """
+ SELECT *
+ FROM ChatMessages
+ WHERE internalConversationId = :internalConversationId
+ AND id <= :messageId
+ ORDER BY timestamp DESC, id DESC
+ LIMIT :limit
+ """
+ )
+ fun getMessagesForConversationBeforeAndEqual(
+ internalConversationId: String,
+ messageId: Long,
+ limit: Int
+ ): Flow>
+
+ @Query(
+ """
+ SELECT COUNT(*)
+ FROM ChatMessages
+ WHERE internalConversationId = :internalConversationId
+ AND id BETWEEN :newestMessageId AND :oldestMessageId
+ """
+ )
+ fun getCountBetweenMessageIds(internalConversationId: String, oldestMessageId: Long, newestMessageId: Long): Int
+
+ @Query(
+ """
+ DELETE FROM chatmessages
+ WHERE internalId LIKE :pattern
+ """
+ )
+ fun clearAllMessagesForUser(pattern: String)
+
+ @Query(
+ """
+ DELETE FROM chatmessages
+ WHERE internalConversationId = :internalConversationId
+ AND id < :messageId
+ """
+ )
+ fun deleteMessagesOlderThan(internalConversationId: String, messageId: Long)
+}
diff --git a/app/src/main/java/com/nextcloud/talk/data/database/dao/ConversationsDao.kt b/app/src/main/java/com/nextcloud/talk/data/database/dao/ConversationsDao.kt
new file mode 100644
index 0000000000..07dd553553
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/database/dao/ConversationsDao.kt
@@ -0,0 +1,49 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.database.dao
+
+import androidx.room.Dao
+import androidx.room.Query
+import androidx.room.Update
+import androidx.room.Upsert
+import com.nextcloud.talk.data.database.model.ConversationEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface ConversationsDao {
+ @Query("SELECT * FROM Conversations where accountId = :accountId")
+ fun getConversationsForUser(accountId: Long): Flow>
+
+ @Query("SELECT * FROM Conversations where accountId = :accountId AND token = :token")
+ fun getConversationForUser(accountId: Long, token: String): Flow
+
+ @Upsert
+ fun upsertConversations(conversationEntities: List)
+
+ /**
+ * Deletes rows in the db matching the specified [conversationIds]
+ */
+ @Query(
+ value = """
+ DELETE FROM conversations
+ WHERE internalId in (:conversationIds)
+ """
+ )
+ fun deleteConversations(conversationIds: List)
+
+ @Update
+ fun updateConversation(conversationEntity: ConversationEntity)
+
+ @Query(
+ """
+ DELETE FROM Conversations
+ WHERE accountId = :accountId
+ """
+ )
+ fun clearAllConversationsForUser(accountId: Long)
+}
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
new file mode 100644
index 0000000000..07ef49fbfd
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ChatMessageMapUtils.kt
@@ -0,0 +1,93 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Julius Linus
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+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.data.database.dao.ChatMessagesDao
+import kotlinx.coroutines.flow.first
+
+fun ChatMessageJson.asEntity(accountId: Long) =
+ ChatMessageEntity(
+ // accountId@token@messageId
+ internalId = "$accountId@$token@$id",
+ accountId = accountId,
+ id = id,
+ internalConversationId = "$accountId@$token",
+ message = message!!,
+ token = token!!,
+ actorType = actorType!!,
+ actorId = actorId!!,
+ actorDisplayName = actorDisplayName!!,
+ timestamp = timestamp,
+ messageParameters = messageParameters,
+ systemMessageType = systemMessageType!!,
+ replyable = replyable,
+ parentMessageId = parentMessage?.id,
+ messageType = messageType!!,
+ reactions = reactions,
+ reactionsSelf = reactionsSelf,
+ expirationTimestamp = expirationTimestamp,
+ renderMarkdown = renderMarkdown,
+ lastEditActorDisplayName = lastEditActorDisplayName,
+ lastEditActorId = lastEditActorId,
+ lastEditActorType = lastEditActorType,
+ lastEditTimestamp = lastEditTimestamp,
+ deleted = deleted
+ )
+
+fun ChatMessageEntity.asModel() =
+ ChatMessage(
+ jsonMessageId = id.toInt(),
+ message = message,
+ token = token,
+ actorType = actorType,
+ actorId = actorId,
+ actorDisplayName = actorDisplayName,
+ timestamp = timestamp,
+ messageParameters = messageParameters,
+ systemMessageType = systemMessageType,
+ replyable = replyable,
+ parentMessageId = parentMessageId,
+ messageType = messageType,
+ reactions = reactions,
+ reactionsSelf = reactionsSelf,
+ expirationTimestamp = expirationTimestamp,
+ renderMarkdown = renderMarkdown,
+ lastEditActorDisplayName = lastEditActorDisplayName,
+ lastEditActorId = lastEditActorId,
+ lastEditActorType = lastEditActorType,
+ lastEditTimestamp = lastEditTimestamp,
+ isDeleted = deleted
+ )
+
+fun ChatMessageJson.asModel() =
+ ChatMessage(
+ jsonMessageId = id.toInt(),
+ message = message,
+ token = token,
+ actorType = actorType,
+ actorId = actorId,
+ actorDisplayName = actorDisplayName,
+ timestamp = timestamp,
+ messageParameters = messageParameters,
+ systemMessageType = systemMessageType,
+ replyable = replyable,
+ parentMessageId = parentMessage?.id,
+ messageType = messageType,
+ reactions = reactions,
+ reactionsSelf = reactionsSelf,
+ expirationTimestamp = expirationTimestamp,
+ renderMarkdown = renderMarkdown,
+ lastEditActorDisplayName = lastEditActorDisplayName,
+ lastEditActorId = lastEditActorId,
+ lastEditActorType = lastEditActorType,
+ lastEditTimestamp = lastEditTimestamp,
+ isDeleted = deleted
+ )
diff --git a/app/src/main/java/com/nextcloud/talk/data/database/mappers/ConversationMapUtils.kt b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ConversationMapUtils.kt
new file mode 100644
index 0000000000..37d8fbced4
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/database/mappers/ConversationMapUtils.kt
@@ -0,0 +1,162 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Your Name
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.database.mappers
+
+import com.bluelinelabs.logansquare.LoganSquare
+import com.nextcloud.talk.data.database.model.ConversationEntity
+import com.nextcloud.talk.models.domain.ConversationModel
+import com.nextcloud.talk.models.json.chat.ChatMessageJson
+import com.nextcloud.talk.models.json.conversations.Conversation
+
+fun ConversationModel.asEntity() =
+ ConversationEntity(
+ internalId = internalId,
+ accountId = accountId,
+ token = token,
+ name = name,
+ displayName = displayName,
+ description = description,
+ type = type,
+ lastPing = lastPing,
+ participantType = participantType,
+ hasPassword = hasPassword,
+ sessionId = sessionId,
+ actorId = actorId,
+ actorType = actorType,
+ favorite = favorite,
+ lastActivity = lastActivity,
+ unreadMessages = unreadMessages,
+ unreadMention = unreadMention,
+ lastMessage = lastMessage?.let { LoganSquare.serialize(lastMessage) },
+ objectType = objectType,
+ notificationLevel = notificationLevel,
+ conversationReadOnlyState = conversationReadOnlyState,
+ lobbyState = lobbyState,
+ lobbyTimer = lobbyTimer,
+ lastReadMessage = lastReadMessage,
+ lastCommonReadMessage = lastCommonReadMessage,
+ hasCall = hasCall,
+ callFlag = callFlag,
+ canStartCall = canStartCall,
+ canLeaveConversation = canLeaveConversation,
+ canDeleteConversation = canDeleteConversation,
+ unreadMentionDirect = unreadMentionDirect,
+ notificationCalls = notificationCalls,
+ permissions = permissions,
+ messageExpiration = messageExpiration,
+ status = status,
+ statusIcon = statusIcon,
+ statusMessage = statusMessage,
+ statusClearAt = statusClearAt,
+ callRecording = callRecording,
+ avatarVersion = avatarVersion,
+ hasCustomAvatar = hasCustomAvatar,
+ callStartTime = callStartTime,
+ recordingConsentRequired = recordingConsentRequired,
+ remoteServer = remoteServer,
+ remoteToken = remoteToken
+ )
+
+fun ConversationEntity.asModel() =
+ ConversationModel(
+ internalId = internalId,
+ accountId = accountId,
+ token = token,
+ name = name,
+ displayName = displayName,
+ description = description,
+ type = type,
+ lastPing = lastPing,
+ participantType = participantType,
+ hasPassword = hasPassword,
+ sessionId = sessionId,
+ actorId = actorId,
+ actorType = actorType,
+ favorite = favorite,
+ lastActivity = lastActivity,
+ unreadMessages = unreadMessages,
+ unreadMention = unreadMention,
+ lastMessage = lastMessage?.let
+ { LoganSquare.parse(lastMessage, ChatMessageJson::class.java) },
+ objectType = objectType,
+ notificationLevel = notificationLevel,
+ conversationReadOnlyState = conversationReadOnlyState,
+ lobbyState = lobbyState,
+ lobbyTimer = lobbyTimer,
+ lastReadMessage = lastReadMessage,
+ lastCommonReadMessage = lastCommonReadMessage,
+ hasCall = hasCall,
+ callFlag = callFlag,
+ canStartCall = canStartCall,
+ canLeaveConversation = canLeaveConversation,
+ canDeleteConversation = canDeleteConversation,
+ unreadMentionDirect = unreadMentionDirect,
+ notificationCalls = notificationCalls,
+ permissions = permissions,
+ messageExpiration = messageExpiration,
+ status = status,
+ statusIcon = statusIcon,
+ statusMessage = statusMessage,
+ statusClearAt = statusClearAt,
+ callRecording = callRecording,
+ avatarVersion = avatarVersion,
+ hasCustomAvatar = hasCustomAvatar,
+ callStartTime = callStartTime,
+ recordingConsentRequired = recordingConsentRequired,
+ remoteServer = remoteServer,
+ remoteToken = remoteToken
+ )
+
+fun Conversation.asEntity(accountId: Long) =
+ ConversationEntity(
+ internalId = "$accountId@$token",
+ accountId = accountId,
+ token = token!!,
+ name = name!!,
+ displayName = displayName!!,
+ description = description!!,
+ type = type!!,
+ lastPing = lastPing,
+ participantType = participantType!!,
+ hasPassword = hasPassword,
+ sessionId = sessionId!!,
+ actorId = actorId!!,
+ actorType = actorType!!,
+ favorite = favorite,
+ lastActivity = lastActivity,
+ unreadMessages = unreadMessages,
+ unreadMention = unreadMention,
+ lastMessage = lastMessage?.let { LoganSquare.serialize(lastMessage) },
+ objectType = objectType!!,
+ notificationLevel = notificationLevel!!,
+ conversationReadOnlyState = conversationReadOnlyState!!,
+ lobbyState = lobbyState!!,
+ lobbyTimer = lobbyTimer!!,
+ lastReadMessage = lastReadMessage,
+ lastCommonReadMessage = lastCommonReadMessage,
+ hasCall = hasCall,
+ callFlag = callFlag,
+ canStartCall = canStartCall,
+ canLeaveConversation = canLeaveConversation!!,
+ canDeleteConversation = canDeleteConversation!!,
+ unreadMentionDirect = unreadMentionDirect!!,
+ notificationCalls = notificationCalls!!,
+ permissions = permissions,
+ messageExpiration = messageExpiration,
+ status = status,
+ statusIcon = statusIcon,
+ statusMessage = statusMessage,
+ statusClearAt = statusClearAt,
+ callRecording = callRecording,
+ avatarVersion = avatarVersion!!,
+ hasCustomAvatar = hasCustomAvatar!!,
+ callStartTime = callStartTime!!,
+ recordingConsentRequired = recordingConsentRequired,
+ remoteServer = remoteServer,
+ remoteToken = remoteToken
+ )
diff --git a/app/src/main/java/com/nextcloud/talk/data/database/model/ChatBlockEntity.kt b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatBlockEntity.kt
new file mode 100644
index 0000000000..15b2008791
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatBlockEntity.kt
@@ -0,0 +1,41 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.database.model
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Index
+import androidx.room.PrimaryKey
+
+@Entity(
+ tableName = "ChatBlocks",
+ foreignKeys = [
+ ForeignKey(
+ entity = ConversationEntity::class,
+ parentColumns = arrayOf("internalId"),
+ childColumns = arrayOf("internalConversationId"),
+ onDelete = ForeignKey.CASCADE,
+ onUpdate = ForeignKey.CASCADE
+ )
+ ],
+ indices = [
+ Index(value = ["internalConversationId"])
+ ]
+)
+data class ChatBlockEntity(
+ @PrimaryKey(autoGenerate = true)
+ @ColumnInfo(name = "id") var id: Long = 0,
+ // accountId@token
+ @ColumnInfo(name = "internalConversationId") var internalConversationId: String,
+ @ColumnInfo(name = "accountId") var accountId: Long? = null,
+ @ColumnInfo(name = "token") var token: String?,
+ @ColumnInfo(name = "oldestMessageId") var oldestMessageId: Long,
+ @ColumnInfo(name = "newestMessageId") var newestMessageId: Long,
+ @ColumnInfo(name = "hasHistory") var hasHistory: Boolean
+)
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
new file mode 100644
index 0000000000..817adda143
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/database/model/ChatMessageEntity.kt
@@ -0,0 +1,69 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.database.model
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Index
+import androidx.room.PrimaryKey
+import com.nextcloud.talk.chat.data.model.ChatMessage
+
+@Entity(
+ tableName = "ChatMessages",
+ foreignKeys = [
+ ForeignKey(
+ entity = ConversationEntity::class,
+ parentColumns = arrayOf("internalId"),
+ childColumns = arrayOf("internalConversationId"),
+ onDelete = ForeignKey.CASCADE,
+ onUpdate = ForeignKey.CASCADE
+ )
+ ],
+ indices = [
+ Index(value = ["internalId"], unique = true),
+ Index(value = ["internalConversationId"])
+ ]
+)
+data class ChatMessageEntity(
+ // MOST IMPORTANT ATTRIBUTES
+
+ @PrimaryKey
+ // accountId@roomtoken@messageId
+ @ColumnInfo(name = "internalId") var internalId: String,
+ @ColumnInfo(name = "accountId") var accountId: Long,
+ @ColumnInfo(name = "token") var token: String,
+ @ColumnInfo(name = "id") var id: Long = 0,
+ // accountId@roomtoken
+ @ColumnInfo(name = "internalConversationId") var internalConversationId: String,
+
+ @ColumnInfo(name = "actorDisplayName") var actorDisplayName: String,
+ @ColumnInfo(name = "message") var message: String,
+
+ // OTHER ATTRIBUTES IN ALPHABETICAL ORDER
+
+ @ColumnInfo(name = "actorId") var actorId: String,
+ @ColumnInfo(name = "actorType") var actorType: String,
+ @ColumnInfo(name = "deleted") var deleted: Boolean = false,
+ @ColumnInfo(name = "expirationTimestamp") var expirationTimestamp: Int = 0,
+ @ColumnInfo(name = "isReplyable") var replyable: Boolean = false,
+ @ColumnInfo(name = "lastEditActorDisplayName") var lastEditActorDisplayName: String? = null,
+ @ColumnInfo(name = "lastEditActorId") var lastEditActorId: String? = null,
+ @ColumnInfo(name = "lastEditActorType") var lastEditActorType: String? = null,
+ @ColumnInfo(name = "lastEditTimestamp") var lastEditTimestamp: Long? = 0,
+ @ColumnInfo(name = "markdown") var renderMarkdown: Boolean? = false,
+ @ColumnInfo(name = "messageParameters") var messageParameters: HashMap>? = null,
+ @ColumnInfo(name = "messageType") var messageType: String,
+ @ColumnInfo(name = "parent") var parentMessageId: Long? = null,
+ @ColumnInfo(name = "reactions") var reactions: LinkedHashMap? = null,
+ @ColumnInfo(name = "reactionsSelf") var reactionsSelf: ArrayList? = 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/database/model/ConversationEntity.kt b/app/src/main/java/com/nextcloud/talk/data/database/model/ConversationEntity.kt
new file mode 100644
index 0000000000..56d840c8dd
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/database/model/ConversationEntity.kt
@@ -0,0 +1,111 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.database.model
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Index
+import androidx.room.PrimaryKey
+import com.nextcloud.talk.data.user.model.UserEntity
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
+import com.nextcloud.talk.models.json.participants.Participant
+
+@Entity(
+ tableName = "Conversations",
+ foreignKeys = [
+ ForeignKey(
+ entity = UserEntity::class,
+ parentColumns = arrayOf("id"),
+ childColumns = arrayOf("accountId"),
+ onDelete = ForeignKey.CASCADE,
+ onUpdate = ForeignKey.CASCADE
+ )
+ ],
+ indices = [
+ Index(value = ["accountId"])
+ ]
+)
+data class ConversationEntity(
+ // MOST IMPORTANT ATTRIBUTES
+
+ @PrimaryKey
+ @ColumnInfo(name = "internalId")
+ var internalId: String,
+
+ // Defines to which talk app account this conversation belongs to
+ @ColumnInfo(name = "accountId") var accountId: Long,
+
+ // We don't use token as primary key as we have to manage multiple talk app accounts on
+ // the phone, thus multiple accounts can have the same conversation in their list. That's why the servers
+ // conversation token is not suitable as primary key on the phone. Also the conversation attributes such as
+ // "unread message" etc only match a specific account.
+ // If multiple talk app accounts have the same conversation, it is stored as another dataset, which is
+ // exactly what we want for this case.
+ @ColumnInfo(name = "token") var token: String,
+
+ @ColumnInfo(name = "displayName") var displayName: String,
+
+ // OTHER ATTRIBUTES IN ALPHABETICAL ORDER
+ @ColumnInfo(name = "actorId") var actorId: String,
+ @ColumnInfo(name = "actorType") var actorType: String,
+ @ColumnInfo(name = "avatarVersion") var avatarVersion: String,
+ @ColumnInfo(name = "callFlag") var callFlag: Int = 0,
+ @ColumnInfo(name = "callRecording") var callRecording: Int = 0,
+ @ColumnInfo(name = "callStartTime") var callStartTime: Long = 0,
+ @ColumnInfo(name = "canDeleteConversation") var canDeleteConversation: Boolean,
+ @ColumnInfo(name = "canLeaveConversation") var canLeaveConversation: Boolean,
+ @ColumnInfo(name = "canStartCall") var canStartCall: Boolean = false,
+ @ColumnInfo(name = "description") var description: String,
+ @ColumnInfo(name = "hasCall") var hasCall: Boolean = false,
+ @ColumnInfo(name = "hasPassword") var hasPassword: Boolean = false,
+ @ColumnInfo(name = "isCustomAvatar") var hasCustomAvatar: Boolean,
+ @ColumnInfo(name = "isFavorite") var favorite: Boolean = false,
+ @ColumnInfo(name = "lastActivity") var lastActivity: Long = 0,
+ @ColumnInfo(name = "lastCommonReadMessage") var lastCommonReadMessage: Int = 0,
+ @ColumnInfo(name = "lastMessage") var lastMessage: String? = null,
+ @ColumnInfo(name = "lastPing") var lastPing: Long = 0,
+ @ColumnInfo(name = "lastReadMessage") var lastReadMessage: Int = 0,
+ @ColumnInfo(name = "lobbyState") var lobbyState: ConversationEnums.LobbyState,
+ @ColumnInfo(name = "lobbyTimer") var lobbyTimer: Long = 0,
+ @ColumnInfo(name = "messageExpiration") var messageExpiration: Int = 0,
+ @ColumnInfo(name = "name") var name: String,
+ @ColumnInfo(name = "notificationCalls") var notificationCalls: Int = 0,
+ @ColumnInfo(name = "notificationLevel") var notificationLevel: ConversationEnums.NotificationLevel,
+ @ColumnInfo(name = "objectType") var objectType: ConversationEnums.ObjectType,
+ @ColumnInfo(name = "participantType") var participantType: Participant.ParticipantType,
+ @ColumnInfo(name = "permissions") var permissions: Int = 0,
+ @ColumnInfo(name = "readOnly") var conversationReadOnlyState: ConversationEnums.ConversationReadOnlyState,
+ @ColumnInfo(name = "recordingConsent") var recordingConsentRequired: Int = 0,
+ @ColumnInfo(name = "remoteServer") var remoteServer: String? = null,
+ @ColumnInfo(name = "remoteToken") var remoteToken: String? = null,
+ @ColumnInfo(name = "sessionId") var sessionId: String,
+ @ColumnInfo(name = "status") var status: String? = null,
+ @ColumnInfo(name = "statusClearAt") var statusClearAt: Long? = 0,
+ @ColumnInfo(name = "statusIcon") var statusIcon: String? = null,
+ @ColumnInfo(name = "statusMessage") var statusMessage: String? = null,
+ @ColumnInfo(name = "type") var type: ConversationEnums.ConversationType,
+ @ColumnInfo(name = "unreadMention") var unreadMention: Boolean = false,
+ @ColumnInfo(name = "unreadMentionDirect") var unreadMentionDirect: Boolean,
+ @ColumnInfo(name = "unreadMessages") var unreadMessages: Int = 0,
+ // missing/not needed: attendeeId
+ // missing/not needed: attendeePin
+ // missing/not needed: attendeePermissions
+ // missing/not needed: callPermissions
+ // missing/not needed: defaultPermissions
+ // missing/not needed: participantInCall
+ // missing/not needed: participantFlags
+ // missing/not needed: listable
+ // missing/not needed: count
+ // missing/not needed: numGuests
+ // missing/not needed: sipEnabled
+ // missing/not needed: canEnableSIP
+ // missing/not needed: objectId
+ // missing/not needed: breakoutRoomMode
+ // missing/not needed: breakoutRoomStatus
+)
diff --git a/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitor.kt b/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitor.kt
new file mode 100644
index 0000000000..b83f4e2b54
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitor.kt
@@ -0,0 +1,17 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Julius Linus
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.network
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Utility for reporting app connectivity status.
+ */
+interface NetworkMonitor {
+ val isOnline: Flow
+}
diff --git a/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitorImpl.kt b/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitorImpl.kt
new file mode 100644
index 0000000000..d3a77013d2
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/network/NetworkMonitorImpl.kt
@@ -0,0 +1,83 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Julius Linus
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.network
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.net.NetworkRequest.Builder
+import androidx.core.content.getSystemService
+import androidx.core.os.trace
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.flowOn
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class NetworkMonitorImpl @Inject constructor(
+ private val context: Context
+) : NetworkMonitor {
+ override val isOnline: Flow = callbackFlow {
+ trace("NetworkMonitorImpl.callbackFlow") {
+ val connectivityManager = context.getSystemService()
+ if (connectivityManager == null) {
+ channel.trySend(false)
+ channel.close()
+ return@callbackFlow
+ }
+
+ /**
+ * The callback's methods are invoked on changes to *any* network matching the [NetworkRequest],
+ * not just the active network. So we can simply track the presence (or absence) of such [Network].
+ */
+ val callback = object : ConnectivityManager.NetworkCallback() {
+
+ private val networks = mutableSetOf()
+
+ override fun onAvailable(network: Network) {
+ networks += network
+ channel.trySend(true)
+ }
+
+ override fun onLost(network: Network) {
+ networks -= network
+ channel.trySend(networks.isNotEmpty())
+ }
+ }
+
+ trace("NetworkMonitorImpl.registerNetworkCallback") {
+ val request = Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build()
+ connectivityManager.registerNetworkCallback(request, callback)
+ }
+
+ /**
+ * Sends the latest connectivity status to the underlying channel.
+ */
+ channel.trySend(connectivityManager.isCurrentlyConnected())
+
+ awaitClose {
+ connectivityManager.unregisterNetworkCallback(callback)
+ }
+ }
+ }
+ .flowOn(Dispatchers.IO)
+ .conflate()
+
+ private fun ConnectivityManager.isCurrentlyConnected() =
+ activeNetwork
+ ?.let(::getNetworkCapabilities)
+ ?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) ?: false
+}
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 d0c52a77eb..8ee3a979a1 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
@@ -33,6 +33,13 @@ object Migrations {
}
}
+ val MIGRATION_10_11 = object : Migration(10, 11) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ Log.i("Migrations", "Migrating 10 to 11")
+ migrateToOfflineSupport(db)
+ }
+ }
+
fun migrateToRoom(db: SupportSQLiteDatabase) {
db.execSQL(
"CREATE TABLE User_new (" +
@@ -51,6 +58,7 @@ object Migrations {
"PRIMARY KEY(id)" +
")"
)
+
db.execSQL(
"CREATE TABLE ArbitraryStorage_new (" +
"accountIdentifier INTEGER NOT NULL, " +
@@ -110,4 +118,120 @@ object Migrations {
// Change the table name to the correct one
db.execSQL("ALTER TABLE ArbitraryStorage_dualPK RENAME TO ArbitraryStorage")
}
+
+ fun migrateToOfflineSupport(db: SupportSQLiteDatabase) {
+ db.execSQL(
+ "CREATE TABLE IF NOT EXISTS Conversations (" +
+ "`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, " +
+ "PRIMARY KEY(`internalId`), " +
+ "FOREIGN KEY(`accountId`) REFERENCES `User`(`id`) " +
+ "ON UPDATE CASCADE ON DELETE CASCADE " +
+ ")"
+ )
+
+ db.execSQL(
+ "CREATE INDEX IF NOT EXISTS `index_Conversations_accountId` ON `Conversations` (`accountId`)"
+ )
+
+ db.execSQL(
+ "CREATE TABLE IF NOT EXISTS ChatMessages (" +
+ "`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, " +
+ "`systemMessage` TEXT NOT NULL, " +
+ "`timestamp` INTEGER NOT NULL, " +
+ "PRIMARY KEY(`internalId`), " +
+ "FOREIGN KEY(`internalConversationId`) REFERENCES `Conversations`(`internalId`) " +
+ "ON UPDATE CASCADE ON DELETE CASCADE " +
+ ")"
+ )
+
+ db.execSQL(
+ "CREATE UNIQUE INDEX IF NOT EXISTS `index_ChatMessages_internalId` ON `ChatMessages` (`internalId`)"
+ )
+
+ db.execSQL(
+ "CREATE INDEX IF NOT EXISTS `index_ChatMessages_internalConversationId` ON `ChatMessages` (`internalConversationId`)"
+ )
+
+ db.execSQL(
+ "CREATE TABLE IF NOT EXISTS ChatBlocks (" +
+ "`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 " +
+ ")"
+ )
+
+ db.execSQL(
+ "CREATE INDEX IF NOT EXISTS `index_ChatBlocks_internalConversationId` ON `ChatBlocks` (`internalConversationId`)"
+ )
+ }
}
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 c2d305a329..573e812255 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
@@ -1,7 +1,7 @@
/*
* Nextcloud Talk - Android Client
*
- * SPDX-FileCopyrightText: 2023 Marcel Hibbe
+ * SPDX-FileCopyrightText: 2023-2024 Marcel Hibbe
* SPDX-FileCopyrightText: 2022 Andy Scherzinger
* SPDX-FileCopyrightText: 2017-2020 Mario Danic
* SPDX-License-Identifier: GPL-3.0-or-later
@@ -10,15 +10,24 @@ package com.nextcloud.talk.data.source.local
import android.content.Context
import android.util.Log
+import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.sqlite.db.SupportSQLiteDatabase
import com.nextcloud.talk.R
+import com.nextcloud.talk.data.database.dao.ChatBlocksDao
+import com.nextcloud.talk.data.database.dao.ChatMessagesDao
+import com.nextcloud.talk.data.database.dao.ConversationsDao
+import com.nextcloud.talk.data.database.model.ChatBlockEntity
+import com.nextcloud.talk.data.database.model.ChatMessageEntity
+import com.nextcloud.talk.data.database.model.ConversationEntity
+import com.nextcloud.talk.data.source.local.converters.ArrayListConverter
import com.nextcloud.talk.data.source.local.converters.CapabilitiesConverter
import com.nextcloud.talk.data.source.local.converters.ExternalSignalingServerConverter
import com.nextcloud.talk.data.source.local.converters.HashMapHashMapConverter
+import com.nextcloud.talk.data.source.local.converters.LinkedHashMapConverter
import com.nextcloud.talk.data.source.local.converters.PushConfigurationConverter
import com.nextcloud.talk.data.source.local.converters.ServerVersionConverter
import com.nextcloud.talk.data.source.local.converters.SignalingSettingsConverter
@@ -31,13 +40,18 @@ import net.sqlcipher.database.SQLiteDatabase
import net.sqlcipher.database.SQLiteDatabaseHook
import net.sqlcipher.database.SupportFactory
import java.util.Locale
-import androidx.room.AutoMigration
@Database(
- entities = [UserEntity::class, ArbitraryStorageEntity::class],
- version = 10,
+ entities = [
+ UserEntity::class,
+ ArbitraryStorageEntity::class,
+ ConversationEntity::class,
+ ChatMessageEntity::class,
+ ChatBlockEntity::class
+ ],
+ version = 11,
autoMigrations = [
- AutoMigration(from = 9, to = 10)
+ AutoMigration(from = 9, to = 10),
],
exportSchema = true
)
@@ -47,11 +61,16 @@ import androidx.room.AutoMigration
ServerVersionConverter::class,
ExternalSignalingServerConverter::class,
SignalingSettingsConverter::class,
- HashMapHashMapConverter::class
+ HashMapHashMapConverter::class,
+ LinkedHashMapConverter::class,
+ ArrayListConverter::class
)
abstract class TalkDatabase : RoomDatabase() {
abstract fun usersDao(): UsersDao
+ abstract fun conversationsDao(): ConversationsDao
+ abstract fun chatMessagesDao(): ChatMessagesDao
+ abstract fun chatBlocksDao(): ChatBlocksDao
abstract fun arbitraryStoragesDao(): ArbitraryStoragesDao
companion object {
@@ -90,7 +109,12 @@ abstract class TalkDatabase : RoomDatabase() {
.databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName)
// comment out openHelperFactory to view the database entries in Android Studio for debugging
.openHelperFactory(factory)
- .addMigrations(Migrations.MIGRATION_6_8, Migrations.MIGRATION_7_8, Migrations.MIGRATION_8_9)
+ .addMigrations(
+ Migrations.MIGRATION_6_8,
+ Migrations.MIGRATION_7_8,
+ Migrations.MIGRATION_8_9,
+ Migrations.MIGRATION_10_11
+ )
.allowMainThreadQueries()
.addCallback(
object : RoomDatabase.Callback() {
diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/ArrayListConverter.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/ArrayListConverter.kt
new file mode 100644
index 0000000000..2248c3fc91
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/ArrayListConverter.kt
@@ -0,0 +1,38 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Julius Linus
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.source.local.converters
+
+import android.util.Log
+import androidx.room.TypeConverter
+import com.bluelinelabs.logansquare.LoganSquare
+
+class ArrayListConverter {
+
+ @TypeConverter
+ fun arrayListToString(list: ArrayList?): String? {
+ return if (list == null) {
+ null
+ } else {
+ return try {
+ LoganSquare.serialize(list)
+ } catch (e: Exception) {
+ Log.e("ArrayListConverter", "Error parsing array list $list to String $e")
+ ""
+ }
+ }
+ }
+
+ @TypeConverter
+ fun stringToArrayList(value: String?): ArrayList? {
+ if (value.isNullOrEmpty()) {
+ return null
+ }
+
+ return LoganSquare.parseList(value, List::class.java) as ArrayList?
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/HashMapHashMapConverter.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/HashMapHashMapConverter.kt
index e1ee647729..3e0a10cd00 100644
--- a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/HashMapHashMapConverter.kt
+++ b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/HashMapHashMapConverter.kt
@@ -12,7 +12,7 @@ import com.bluelinelabs.logansquare.LoganSquare
class HashMapHashMapConverter {
@TypeConverter
- fun fromDoubleHashMapToString(map: HashMap>?): String? {
+ fun fromDoubleHashMapToString(map: HashMap>?): String? {
return if (map == null) {
LoganSquare.serialize(hashMapOf>())
} else {
@@ -21,11 +21,11 @@ class HashMapHashMapConverter {
}
@TypeConverter
- fun fromStringToDoubleHashMap(value: String?): HashMap>? {
+ fun fromStringToDoubleHashMap(value: String?): HashMap>? {
if (value.isNullOrEmpty()) {
return hashMapOf()
}
- return LoganSquare.parseMap(value, HashMap::class.java) as HashMap>?
+ return LoganSquare.parseMap(value, HashMap::class.java) as HashMap>?
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/LinkedHashMapConverter.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/LinkedHashMapConverter.kt
new file mode 100644
index 0000000000..206d3dd8c5
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/LinkedHashMapConverter.kt
@@ -0,0 +1,59 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.source.local.converters
+
+import android.util.Log
+import androidx.room.TypeConverter
+import com.fasterxml.jackson.core.JsonFactory
+import java.io.IOException
+
+class LinkedHashMapConverter {
+
+ private val converter = LinkedHashMapStringIntConverter()
+ private val jsonFactory = JsonFactory()
+
+ @TypeConverter
+ fun stringToLinkedHashMap(value: String?): LinkedHashMap {
+ if (value.isNullOrEmpty() || value == "{}") {
+ return linkedMapOf()
+ }
+ // "{"👍":1,"👎":1,"😃":1,"😯":1}" // pretend this is value
+ return try {
+ val map = linkedMapOf()
+ val trimmed = value.replace("{", "").replace("}", "")
+ // "👍":1,"👎":1,"😃":1,"😯":1
+ val mapList = trimmed.split(",")
+ // ["👍":1]["👎":1]["😃":1]["😯":1]
+ for (mapStr in mapList) {
+ val emojiMapList = mapStr.split(":")
+ val emoji = emojiMapList[0].replace("\"", "") // removes double quotes
+ val count = emojiMapList[1].toInt()
+ map[emoji] = count
+ }
+ // [👍:1],[👎:1],[😃:1],[😯:1]
+ return map
+ } catch (e: IOException) {
+ Log.e("LinkedHashMapConverter", "Error parsing string: $value to linkedHashMap $e")
+ linkedMapOf()
+ }
+ }
+
+ @TypeConverter
+ fun linkedHashMapToString(map: LinkedHashMap?): String {
+ return try {
+ val stringWriter = java.io.StringWriter()
+ jsonFactory.createGenerator(stringWriter).use { generator ->
+ converter.serialize(map ?: linkedMapOf(), null, false, generator)
+ }
+ stringWriter.toString()
+ } catch (e: IOException) {
+ // e.printStackTrace()
+ ""
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/data/source/local/converters/LinkedHashMapStringIntConverter.kt b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/LinkedHashMapStringIntConverter.kt
new file mode 100644
index 0000000000..feed96cbab
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/data/source/local/converters/LinkedHashMapStringIntConverter.kt
@@ -0,0 +1,50 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.data.source.local.converters
+
+import com.bluelinelabs.logansquare.typeconverters.TypeConverter
+import com.fasterxml.jackson.core.JsonParser
+import com.fasterxml.jackson.core.JsonGenerator
+import java.io.IOException
+
+class LinkedHashMapStringIntConverter : TypeConverter> {
+
+ @Throws(IOException::class)
+ override fun parse(jsonParser: JsonParser?): LinkedHashMap {
+ val map: LinkedHashMap = linkedMapOf()
+ jsonParser?.apply {
+ while (nextToken() != null) {
+ val key = text
+ nextToken()
+ val value = intValue
+ map[key] = value
+ }
+ }
+ return map
+ }
+
+ @Throws(IOException::class)
+ override fun serialize(
+ `object`: LinkedHashMap?,
+ fieldName: String?,
+ writeFieldNameForObject: Boolean,
+ jsonGenerator: JsonGenerator?
+ ) {
+ jsonGenerator?.apply {
+ if (fieldName != null) {
+ writeFieldName(fieldName)
+ }
+ writeStartObject()
+ `object`?.forEach { (key, value) ->
+ writeFieldName(key)
+ writeNumber(value)
+ }
+ writeEndObject()
+ }
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt b/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt
index 1a544552e5..ae1dc18568 100644
--- a/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt
+++ b/app/src/main/java/com/nextcloud/talk/extensions/ImageViewExtensions.kt
@@ -29,9 +29,9 @@ import coil.transform.RoundedCornersTransformation
import com.nextcloud.talk.R
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
-import com.nextcloud.talk.models.domain.ConversationType
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
@@ -49,7 +49,7 @@ fun ImageView.loadConversationAvatar(
): io.reactivex.disposables.Disposable {
return loadConversationAvatar(
user,
- ConversationModel.mapToConversationModel(conversation),
+ ConversationModel.mapToConversationModel(conversation, user),
ignoreCache,
viewThemeUtils
)
@@ -72,10 +72,10 @@ fun ImageView.loadConversationAvatar(
if (conversation.avatarVersion.isNullOrEmpty() && viewThemeUtils != null) {
when (conversation.type) {
- ConversationType.ROOM_GROUP_CALL ->
+ ConversationEnums.ConversationType.ROOM_GROUP_CALL ->
return loadDefaultGroupCallAvatar(viewThemeUtils)
- ConversationType.ROOM_PUBLIC_CALL ->
+ ConversationEnums.ConversationType.ROOM_PUBLIC_CALL ->
return loadDefaultPublicCallAvatar(viewThemeUtils)
else -> {}
@@ -86,10 +86,10 @@ fun ImageView.loadConversationAvatar(
// when no own images are set. (although these default avatars can not be themed for the android app..)
val errorPlaceholder =
when (conversation.type) {
- ConversationType.ROOM_GROUP_CALL ->
+ ConversationEnums.ConversationType.ROOM_GROUP_CALL ->
ContextCompat.getDrawable(context, R.drawable.ic_circular_group)
- ConversationType.ROOM_PUBLIC_CALL ->
+ ConversationEnums.ConversationType.ROOM_PUBLIC_CALL ->
ContextCompat.getDrawable(context, R.drawable.ic_circular_link)
else -> ContextCompat.getDrawable(context, R.drawable.account_circle_96dp)
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 b60332775e..18782a5be1 100644
--- a/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java
+++ b/app/src/main/java/com/nextcloud/talk/jobs/AccountRemovalWorker.java
@@ -16,6 +16,9 @@
import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager;
+import com.nextcloud.talk.data.database.dao.ChatBlocksDao;
+import com.nextcloud.talk.data.database.dao.ChatMessagesDao;
+import com.nextcloud.talk.data.database.dao.ConversationsDao;
import com.nextcloud.talk.data.user.model.User;
import com.nextcloud.talk.models.json.generic.GenericMeta;
import com.nextcloud.talk.models.json.generic.GenericOverall;
@@ -46,17 +49,19 @@
public class AccountRemovalWorker extends Worker {
public static final String TAG = "AccountRemovalWorker";
- @Inject
- UserManager userManager;
+ @Inject UserManager userManager;
- @Inject
- ArbitraryStorageManager arbitraryStorageManager;
+ @Inject ArbitraryStorageManager arbitraryStorageManager;
- @Inject
- Retrofit retrofit;
+ @Inject Retrofit retrofit;
- @Inject
- OkHttpClient okHttpClient;
+ @Inject OkHttpClient okHttpClient;
+
+ @Inject ChatMessagesDao chatMessagesDao;
+
+ @Inject ConversationsDao conversationsDao;
+
+ @Inject ChatBlocksDao chatBlocksDao;
NcApi ncApi;
diff --git a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt
index d500b2c9a4..870bb94194 100644
--- a/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt
+++ b/app/src/main/java/com/nextcloud/talk/jobs/NotificationWorker.kt
@@ -49,11 +49,11 @@ import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
import com.nextcloud.talk.callnotification.CallNotificationActivity
-import com.nextcloud.talk.chat.data.ChatRepository
+import com.nextcloud.talk.chat.data.network.ChatNetworkDataSource
import com.nextcloud.talk.models.SignatureVerification
import com.nextcloud.talk.models.domain.ConversationModel
-import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.json.chat.ChatUtils.Companion.getParsedMessage
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.notifications.NotificationOverall
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.models.json.participants.ParticipantsOverall
@@ -125,7 +125,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
@Inject
var retrofit: Retrofit? = null
- var chatRepository: ChatRepository? = null
+ var chatNetworkDataSource: ChatNetworkDataSource? = null
@Inject set
@Inject
@@ -231,7 +231,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
bundle.putLong(KEY_INTERNAL_USER_ID, signatureVerification.user!!.id!!)
bundle.putBoolean(KEY_FROM_NOTIFICATION_START_CALL, true)
- val isOneToOneCall = conversation.type === ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+ val isOneToOneCall = conversation.type === ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
bundle.putBoolean(KEY_ROOM_ONE_TO_ONE, isOneToOneCall) // ggf change in Activity? not necessary????
bundle.putString(BundleKeys.KEY_CONVERSATION_NAME, conversation.name)
@@ -300,7 +300,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
checkIfCallIsActive(signatureVerification, conversation)
}
- chatRepository?.getRoom(userBeingCalled, roomToken = pushMessage.id!!)
+ chatNetworkDataSource?.getRoom(userBeingCalled, roomToken = pushMessage.id!!)
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe(object : Observer {
diff --git a/app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt b/app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt
index 1e7690ef0b..2da0f9db46 100644
--- a/app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/domain/ConversationModel.kt
@@ -7,40 +7,46 @@
*/
package com.nextcloud.talk.models.domain
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.models.json.chat.ChatMessageJson
import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
+import com.nextcloud.talk.models.json.participants.Participant
class ConversationModel(
- var roomId: String? = null,
- var token: String? = null,
- var name: String? = null,
- var displayName: String? = null,
- var description: String? = null,
- var type: ConversationType? = null,
+ var internalId: String,
+ var accountId: Long,
+ // var roomId: String? = null,
+ var token: String,
+ var name: String,
+ var displayName: String,
+ var description: String,
+ var type: ConversationEnums.ConversationType,
var lastPing: Long = 0,
- var participantType: ParticipantType? = null,
+ var participantType: Participant.ParticipantType,
var hasPassword: Boolean = false,
- var sessionId: String? = null,
- var actorId: String? = null,
- var actorType: String? = null,
- var password: String? = null,
+ var sessionId: String,
+ var actorId: String,
+ var actorType: String,
var favorite: Boolean = false,
var lastActivity: Long = 0,
var unreadMessages: Int = 0,
var unreadMention: Boolean = false,
- // var lastMessage: .....? = null,
- var objectType: ObjectType? = null,
- var notificationLevel: NotificationLevel? = null,
- var conversationReadOnlyState: ConversationReadOnlyState? = null,
- var lobbyState: LobbyState? = null,
- var lobbyTimer: Long? = null,
+ var lastMessage: ChatMessageJson? = null,
+ var objectType: ConversationEnums.ObjectType,
+ var notificationLevel: ConversationEnums.NotificationLevel,
+ var conversationReadOnlyState: ConversationEnums.ConversationReadOnlyState,
+ var lobbyState: ConversationEnums.LobbyState,
+ var lobbyTimer: Long,
var lastReadMessage: Int = 0,
+ var lastCommonReadMessage: Int = 0,
var hasCall: Boolean = false,
var callFlag: Int = 0,
var canStartCall: Boolean = false,
- var canLeaveConversation: Boolean? = null,
- var canDeleteConversation: Boolean? = null,
- var unreadMentionDirect: Boolean? = null,
- var notificationCalls: Int? = null,
+ var canLeaveConversation: Boolean,
+ var canDeleteConversation: Boolean,
+ var unreadMentionDirect: Boolean,
+ var notificationCalls: Int,
var permissions: Int = 0,
var messageExpiration: Int = 0,
var status: String? = null,
@@ -48,56 +54,62 @@ class ConversationModel(
var statusMessage: String? = null,
var statusClearAt: Long? = 0,
var callRecording: Int = 0,
- var avatarVersion: String? = null,
- var hasCustomAvatar: Boolean? = null,
- var callStartTime: Long? = null,
+ var avatarVersion: String,
+ var hasCustomAvatar: Boolean,
+ var callStartTime: Long,
var recordingConsentRequired: Int = 0,
var remoteServer: String? = null,
- var remoteToken: String? = null
+ var remoteToken: String? = null,
+
+ // attributes that don't come from API. This should be changed?!
+ var password: String? = null,
) {
companion object {
- fun mapToConversationModel(conversation: Conversation): ConversationModel {
+ fun mapToConversationModel(conversation: Conversation, user: User): ConversationModel {
return ConversationModel(
- roomId = conversation.roomId,
- token = conversation.token,
- name = conversation.name,
- displayName = conversation.displayName,
- description = conversation.description,
- type = conversation.type?.let { ConversationType.valueOf(it.name) },
+ internalId = user.id!!.toString() + "@" + conversation.token,
+ accountId = user.id!!,
+ // roomId = conversation.roomId,
+ token = conversation.token!!,
+ name = conversation.name!!,
+ displayName = conversation.displayName!!,
+ description = conversation.description!!,
+ type = conversation.type.let { ConversationEnums.ConversationType.valueOf(it!!.name) },
lastPing = conversation.lastPing,
- participantType = conversation.participantType?.let { ParticipantType.valueOf(it.name) },
+ participantType = conversation.participantType.let { Participant.ParticipantType.valueOf(it!!.name) },
hasPassword = conversation.hasPassword,
- sessionId = conversation.sessionId,
- actorId = conversation.actorId,
- actorType = conversation.actorType,
+ sessionId = conversation.sessionId!!,
+ actorId = conversation.actorId!!,
+ actorType = conversation.actorType!!,
password = conversation.password,
favorite = conversation.favorite,
lastActivity = conversation.lastActivity,
unreadMessages = conversation.unreadMessages,
unreadMention = conversation.unreadMention,
- // lastMessage = conversation.lastMessage, to do...
- objectType = conversation.objectType?.let { ObjectType.valueOf(it.name) },
- notificationLevel = conversation.notificationLevel?.let {
- NotificationLevel.valueOf(
- it.name
+ lastMessage = conversation.lastMessage,
+ objectType = conversation.objectType.let { ConversationEnums.ObjectType.valueOf(it!!.name) },
+ notificationLevel = conversation.notificationLevel.let {
+ ConversationEnums.NotificationLevel.valueOf(
+ it!!.name
)
},
- conversationReadOnlyState = conversation.conversationReadOnlyState?.let {
- ConversationReadOnlyState.valueOf(
- it.name
+ conversationReadOnlyState = conversation.conversationReadOnlyState.let {
+ ConversationEnums.ConversationReadOnlyState.valueOf(
+ it!!.name
)
},
- lobbyState = conversation.lobbyState?.let { LobbyState.valueOf(it.name) },
- lobbyTimer = conversation.lobbyTimer,
+ lobbyState = conversation.lobbyState.let { ConversationEnums.LobbyState.valueOf(it!!.name) },
+ lobbyTimer = conversation.lobbyTimer!!,
lastReadMessage = conversation.lastReadMessage,
+ lastCommonReadMessage = conversation.lastCommonReadMessage,
hasCall = conversation.hasCall,
callFlag = conversation.callFlag,
canStartCall = conversation.canStartCall,
- canLeaveConversation = conversation.canLeaveConversation,
- canDeleteConversation = conversation.canDeleteConversation,
- unreadMentionDirect = conversation.unreadMentionDirect,
- notificationCalls = conversation.notificationCalls,
+ canLeaveConversation = conversation.canLeaveConversation!!,
+ canDeleteConversation = conversation.canDeleteConversation!!,
+ unreadMentionDirect = conversation.unreadMentionDirect!!,
+ notificationCalls = conversation.notificationCalls!!,
permissions = conversation.permissions,
messageExpiration = conversation.messageExpiration,
status = conversation.status,
@@ -105,9 +117,9 @@ class ConversationModel(
statusMessage = conversation.statusMessage,
statusClearAt = conversation.statusClearAt,
callRecording = conversation.callRecording,
- avatarVersion = conversation.avatarVersion,
- hasCustomAvatar = conversation.hasCustomAvatar,
- callStartTime = conversation.callStartTime,
+ avatarVersion = conversation.avatarVersion!!,
+ hasCustomAvatar = conversation.hasCustomAvatar!!,
+ callStartTime = conversation.callStartTime!!,
recordingConsentRequired = conversation.recordingConsentRequired,
remoteServer = conversation.remoteServer,
remoteToken = conversation.remoteToken
@@ -116,46 +128,46 @@ class ConversationModel(
}
}
-enum class ConversationType {
- DUMMY,
- ROOM_TYPE_ONE_TO_ONE_CALL,
- ROOM_GROUP_CALL,
- ROOM_PUBLIC_CALL,
- ROOM_SYSTEM,
- FORMER_ONE_TO_ONE,
- NOTE_TO_SELF
-}
-
-enum class ParticipantType {
- DUMMY,
- OWNER,
- MODERATOR,
- USER,
- GUEST,
- USER_FOLLOWING_LINK,
- GUEST_MODERATOR
-}
-
-enum class ObjectType {
- DEFAULT,
- SHARE_PASSWORD,
- FILE,
- ROOM
-}
-
-enum class NotificationLevel {
- DEFAULT,
- ALWAYS,
- MENTION,
- NEVER
-}
-
-enum class ConversationReadOnlyState {
- CONVERSATION_READ_WRITE,
- CONVERSATION_READ_ONLY
-}
-
-enum class LobbyState {
- LOBBY_STATE_ALL_PARTICIPANTS,
- LOBBY_STATE_MODERATORS_ONLY
-}
+// enum class ConversationType {
+// DUMMY,
+// ROOM_TYPE_ONE_TO_ONE_CALL,
+// ROOM_GROUP_CALL,
+// ROOM_PUBLIC_CALL,
+// ROOM_SYSTEM,
+// FORMER_ONE_TO_ONE,
+// NOTE_TO_SELF
+// }
+//
+// enum class ParticipantType {
+// DUMMY,
+// OWNER,
+// MODERATOR,
+// USER,
+// GUEST,
+// USER_FOLLOWING_LINK,
+// GUEST_MODERATOR
+// }
+//
+// enum class ObjectType {
+// DEFAULT,
+// SHARE_PASSWORD,
+// FILE,
+// ROOM
+// }
+//
+// enum class NotificationLevel {
+// DEFAULT,
+// ALWAYS,
+// MENTION,
+// NEVER
+// }
+//
+// enum class ConversationReadOnlyState {
+// CONVERSATION_READ_WRITE,
+// CONVERSATION_READ_ONLY
+// }
+//
+// enum class LobbyState {
+// LOBBY_STATE_ALL_PARTICIPANTS,
+// LOBBY_STATE_MODERATORS_ONLY
+// }
diff --git a/app/src/main/java/com/nextcloud/talk/models/domain/ReactionAddedModel.kt b/app/src/main/java/com/nextcloud/talk/models/domain/ReactionAddedModel.kt
index 35c1160bb5..fe95dc03ae 100644
--- a/app/src/main/java/com/nextcloud/talk/models/domain/ReactionAddedModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/domain/ReactionAddedModel.kt
@@ -6,7 +6,7 @@
*/
package com.nextcloud.talk.models.domain
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
data class ReactionAddedModel(
var chatMessage: ChatMessage,
diff --git a/app/src/main/java/com/nextcloud/talk/models/domain/ReactionDeletedModel.kt b/app/src/main/java/com/nextcloud/talk/models/domain/ReactionDeletedModel.kt
index 8f7b97f027..869207a2bc 100644
--- a/app/src/main/java/com/nextcloud/talk/models/domain/ReactionDeletedModel.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/domain/ReactionDeletedModel.kt
@@ -6,7 +6,7 @@
*/
package com.nextcloud.talk.models.domain
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
data class ReactionDeletedModel(
var chatMessage: ChatMessage,
diff --git a/app/src/main/java/com/nextcloud/talk/models/domain/converters/DomainEnumNotificationLevelConverter.kt b/app/src/main/java/com/nextcloud/talk/models/domain/converters/DomainEnumNotificationLevelConverter.kt
index 889b1d2274..2735af4bf4 100644
--- a/app/src/main/java/com/nextcloud/talk/models/domain/converters/DomainEnumNotificationLevelConverter.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/domain/converters/DomainEnumNotificationLevelConverter.kt
@@ -9,25 +9,25 @@
package com.nextcloud.talk.models.domain.converters
import com.bluelinelabs.logansquare.typeconverters.IntBasedTypeConverter
-import com.nextcloud.talk.models.domain.NotificationLevel
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
-class DomainEnumNotificationLevelConverter : IntBasedTypeConverter() {
- override fun getFromInt(i: Int): NotificationLevel {
+class DomainEnumNotificationLevelConverter : IntBasedTypeConverter() {
+ override fun getFromInt(i: Int): ConversationEnums.NotificationLevel {
return when (i) {
- DEFAULT -> NotificationLevel.DEFAULT
- ALWAYS -> NotificationLevel.ALWAYS
- MENTION -> NotificationLevel.MENTION
- NEVER -> NotificationLevel.NEVER
- else -> NotificationLevel.DEFAULT
+ DEFAULT -> ConversationEnums.NotificationLevel.DEFAULT
+ ALWAYS -> ConversationEnums.NotificationLevel.ALWAYS
+ MENTION -> ConversationEnums.NotificationLevel.MENTION
+ NEVER -> ConversationEnums.NotificationLevel.NEVER
+ else -> ConversationEnums.NotificationLevel.DEFAULT
}
}
- override fun convertToInt(`object`: NotificationLevel): Int {
+ override fun convertToInt(`object`: ConversationEnums.NotificationLevel): Int {
return when (`object`) {
- NotificationLevel.DEFAULT -> DEFAULT
- NotificationLevel.ALWAYS -> ALWAYS
- NotificationLevel.MENTION -> MENTION
- NotificationLevel.NEVER -> NEVER
+ ConversationEnums.NotificationLevel.DEFAULT -> DEFAULT
+ ConversationEnums.NotificationLevel.ALWAYS -> ALWAYS
+ ConversationEnums.NotificationLevel.MENTION -> MENTION
+ ConversationEnums.NotificationLevel.NEVER -> NEVER
else -> DEFAULT
}
}
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
new file mode 100644
index 0000000000..90068d9455
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessageJson.kt
@@ -0,0 +1,46 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Marcel Hibbe
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.models.json.chat
+
+import android.os.Parcelable
+import com.bluelinelabs.logansquare.annotation.JsonField
+import com.bluelinelabs.logansquare.annotation.JsonObject
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType
+import com.nextcloud.talk.models.json.converters.EnumSystemMessageTypeConverter
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+@JsonObject
+data class ChatMessageJson(
+ @JsonField(name = ["id"]) var id: Long = 0,
+ @JsonField(name = ["token"]) var token: String? = null,
+ @JsonField(name = ["actorType"]) var actorType: String? = null,
+ @JsonField(name = ["actorId"]) var actorId: String? = null,
+ @JsonField(name = ["actorDisplayName"]) var actorDisplayName: String? = null,
+ @JsonField(name = ["timestamp"]) var timestamp: Long = 0,
+ @JsonField(name = ["message"]) var message: String? = null,
+
+ @JsonField(name = ["messageParameters"])
+ var messageParameters: HashMap>? = null,
+
+ @JsonField(name = ["systemMessage"], typeConverter = EnumSystemMessageTypeConverter::class)
+ var systemMessageType: SystemMessageType? = null,
+
+ @JsonField(name = ["isReplyable"]) var replyable: Boolean = false,
+ @JsonField(name = ["parent"]) var parentMessage: ChatMessageJson? = null,
+ @JsonField(name = ["messageType"]) var messageType: String? = null,
+ @JsonField(name = ["reactions"]) var reactions: LinkedHashMap? = null,
+ @JsonField(name = ["reactionsSelf"]) var reactionsSelf: ArrayList? = null,
+ @JsonField(name = ["expirationTimestamp"]) var expirationTimestamp: Int = 0,
+ @JsonField(name = ["markdown"]) var renderMarkdown: Boolean? = null,
+ @JsonField(name = ["lastEditActorDisplayName"]) var lastEditActorDisplayName: String? = null,
+ @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,
+) : Parcelable
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOCS.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOCS.kt
index e1db9062cf..d8f27ab989 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOCS.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOCS.kt
@@ -19,7 +19,7 @@ data class ChatOCS(
@JsonField(name = ["meta"])
var meta: GenericMeta?,
@JsonField(name = ["data"])
- var data: List? = null
+ var data: List? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOCSSingleMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOCSSingleMessage.kt
index 8a73e0845c..63b52c5300 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOCSSingleMessage.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatOCSSingleMessage.kt
@@ -19,7 +19,7 @@ data class ChatOCSSingleMessage(
@JsonField(name = ["meta"])
var meta: GenericMeta?,
@JsonField(name = ["data"])
- var data: ChatMessage? = null
+ var data: ChatMessageJson? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatShareOCS.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatShareOCS.kt
index e95750702d..0c8ba7336d 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatShareOCS.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatShareOCS.kt
@@ -10,14 +10,13 @@ package com.nextcloud.talk.models.json.chat
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
-import java.util.HashMap
import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class ChatShareOCS(
@JsonField(name = ["data"])
- var data: HashMap? = null
+ var data: HashMap? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null)
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatUtils.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatUtils.kt
index acc200897f..c8f2da6147 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatUtils.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatUtils.kt
@@ -33,7 +33,7 @@ class ChatUtils {
resultMessage?.replace("{$key}", "@" + individualHashMap["name"])
} else if (type == "geo-location") {
individualHashMap["name"]
- } else if (individualHashMap?.containsKey("link") == true) {
+ } else if (individualHashMap.containsKey("link") == true) {
if (type == "file") {
resultMessage?.replace("{$key}", individualHashMap["name"].toString())
} else {
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt b/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt
index 263e0aa09c..fae0225cae 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/json/conversations/Conversation.kt
@@ -14,7 +14,7 @@ import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.models.json.chat.ChatMessageJson
import com.nextcloud.talk.models.json.converters.ConversationObjectTypeConverter
import com.nextcloud.talk.models.json.converters.EnumLobbyStateConverter
import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter
@@ -28,8 +28,8 @@ import kotlinx.parcelize.Parcelize
@Parcelize
@JsonObject
data class Conversation(
- @JsonField(name = ["id"])
- var roomId: String? = null,
+ // @JsonField(name = ["id"])
+ // var roomId: String? = null,
@JsonField(name = ["token"])
var token: String? = null,
@JsonField(name = ["name"])
@@ -39,7 +39,7 @@ data class Conversation(
@JsonField(name = ["description"])
var description: String? = null,
@JsonField(name = ["type"], typeConverter = EnumRoomTypeConverter::class)
- var type: ConversationType? = null,
+ var type: ConversationEnums.ConversationType? = null,
@JsonField(name = ["lastPing"])
var lastPing: Long = 0,
@JsonField(name = ["participantType"], typeConverter = EnumParticipantTypeConverter::class)
@@ -68,19 +68,19 @@ data class Conversation(
var unreadMention: Boolean = false,
@JsonField(name = ["lastMessage"])
- var lastMessage: ChatMessage? = null,
+ var lastMessage: ChatMessageJson? = null,
@JsonField(name = ["objectType"], typeConverter = ConversationObjectTypeConverter::class)
- var objectType: ObjectType? = null,
+ var objectType: ConversationEnums.ObjectType? = null,
@JsonField(name = ["notificationLevel"], typeConverter = EnumNotificationLevelConverter::class)
- var notificationLevel: NotificationLevel? = null,
+ var notificationLevel: ConversationEnums.NotificationLevel? = null,
@JsonField(name = ["readOnly"], typeConverter = EnumReadOnlyConversationConverter::class)
- var conversationReadOnlyState: ConversationReadOnlyState? = null,
+ var conversationReadOnlyState: ConversationEnums.ConversationReadOnlyState? = null,
@JsonField(name = ["lobbyState"], typeConverter = EnumLobbyStateConverter::class)
- var lobbyState: LobbyState? = null,
+ var lobbyState: ConversationEnums.LobbyState? = null,
@JsonField(name = ["lobbyTimer"])
var lobbyTimer: Long? = null,
@@ -88,6 +88,9 @@ data class Conversation(
@JsonField(name = ["lastReadMessage"])
var lastReadMessage: Int = 0,
+ @JsonField(name = ["lastCommonReadMessage"])
+ var lastCommonReadMessage: Int = 0,
+
@JsonField(name = ["hasCall"])
var hasCall: Boolean = false,
@@ -149,15 +152,12 @@ data class Conversation(
var remoteServer: String? = null,
@JsonField(name = ["remoteToken"])
- var remoteToken: String? = null
+ var remoteToken: String? = null,
) : Parcelable {
- // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
- constructor() : this(null, null)
-
@Deprecated("Use ConversationUtil")
val isPublic: Boolean
- get() = ConversationType.ROOM_PUBLIC_CALL == type
+ get() = ConversationEnums.ConversationType.ROOM_PUBLIC_CALL == type
@Deprecated("Use ConversationUtil")
val isGuest: Boolean
@@ -175,22 +175,27 @@ data class Conversation(
fun canModerate(conversationUser: User): Boolean {
return isParticipantOwnerOrModerator &&
!ConversationUtils.isLockedOneToOne(
- ConversationModel.mapToConversationModel(this),
+ ConversationModel.mapToConversationModel(this, conversationUser),
conversationUser.capabilities?.spreedCapability!!
) &&
- type != ConversationType.FORMER_ONE_TO_ONE &&
- !ConversationUtils.isNoteToSelfConversation(ConversationModel.mapToConversationModel(this))
+ type != ConversationEnums.ConversationType.FORMER_ONE_TO_ONE &&
+ !ConversationUtils.isNoteToSelfConversation(
+ ConversationModel.mapToConversationModel(this, conversationUser)
+ )
}
@Deprecated("Use ConversationUtil")
fun isLobbyViewApplicable(conversationUser: User): Boolean {
return !canModerate(conversationUser) &&
- (type == ConversationType.ROOM_GROUP_CALL || type == ConversationType.ROOM_PUBLIC_CALL)
+ (
+ type == ConversationEnums.ConversationType.ROOM_GROUP_CALL ||
+ type == ConversationEnums.ConversationType.ROOM_PUBLIC_CALL
+ )
}
@Deprecated("Use ConversationUtil")
fun isNameEditable(conversationUser: User): Boolean {
- return canModerate(conversationUser) && ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != type
+ return canModerate(conversationUser) && ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != type
}
@Deprecated("Use ConversationUtil")
@@ -216,41 +221,6 @@ data class Conversation(
@Deprecated("Use ConversationUtil")
fun isNoteToSelfConversation(): Boolean {
- return type == ConversationType.NOTE_TO_SELF
- }
-
- enum class NotificationLevel {
- DEFAULT,
- ALWAYS,
- MENTION,
- NEVER
- }
-
- enum class LobbyState {
- LOBBY_STATE_ALL_PARTICIPANTS,
- LOBBY_STATE_MODERATORS_ONLY
- }
-
- enum class ConversationReadOnlyState {
- CONVERSATION_READ_WRITE,
- CONVERSATION_READ_ONLY
- }
-
- @Parcelize
- enum class ConversationType : Parcelable {
- DUMMY,
- ROOM_TYPE_ONE_TO_ONE_CALL,
- ROOM_GROUP_CALL,
- ROOM_PUBLIC_CALL,
- ROOM_SYSTEM,
- FORMER_ONE_TO_ONE,
- NOTE_TO_SELF
- }
-
- enum class ObjectType {
- DEFAULT,
- SHARE_PASSWORD,
- FILE,
- ROOM
+ return type == ConversationEnums.ConversationType.NOTE_TO_SELF
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/conversations/ConversationEnums.kt b/app/src/main/java/com/nextcloud/talk/models/json/conversations/ConversationEnums.kt
new file mode 100644
index 0000000000..15be0a6644
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/models/json/conversations/ConversationEnums.kt
@@ -0,0 +1,48 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Your Name
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+package com.nextcloud.talk.models.json.conversations
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+class ConversationEnums {
+ enum class NotificationLevel {
+ DEFAULT,
+ ALWAYS,
+ MENTION,
+ NEVER
+ }
+
+ enum class LobbyState {
+ LOBBY_STATE_ALL_PARTICIPANTS,
+ LOBBY_STATE_MODERATORS_ONLY
+ }
+
+ enum class ConversationReadOnlyState {
+ CONVERSATION_READ_WRITE,
+ CONVERSATION_READ_ONLY
+ }
+
+ @Parcelize
+ enum class ConversationType : Parcelable {
+ DUMMY,
+ ROOM_TYPE_ONE_TO_ONE_CALL,
+ ROOM_GROUP_CALL,
+ ROOM_PUBLIC_CALL,
+ ROOM_SYSTEM,
+ FORMER_ONE_TO_ONE,
+ NOTE_TO_SELF
+ }
+
+ enum class ObjectType {
+ DEFAULT,
+ SHARE_PASSWORD,
+ FILE,
+ ROOM
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/converters/ConversationObjectTypeConverter.kt b/app/src/main/java/com/nextcloud/talk/models/json/converters/ConversationObjectTypeConverter.kt
index e1ce5da424..65ffb639a6 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/converters/ConversationObjectTypeConverter.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/json/converters/ConversationObjectTypeConverter.kt
@@ -7,27 +7,27 @@
package com.nextcloud.talk.models.json.converters
import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter
-import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
-class ConversationObjectTypeConverter : StringBasedTypeConverter() {
- override fun getFromString(string: String?): Conversation.ObjectType {
+class ConversationObjectTypeConverter : StringBasedTypeConverter() {
+ override fun getFromString(string: String?): ConversationEnums.ObjectType {
return when (string) {
- "share:password" -> Conversation.ObjectType.SHARE_PASSWORD
- "room" -> Conversation.ObjectType.ROOM
- "file" -> Conversation.ObjectType.FILE
- else -> Conversation.ObjectType.DEFAULT
+ "share:password" -> ConversationEnums.ObjectType.SHARE_PASSWORD
+ "room" -> ConversationEnums.ObjectType.ROOM
+ "file" -> ConversationEnums.ObjectType.FILE
+ else -> ConversationEnums.ObjectType.DEFAULT
}
}
- override fun convertToString(`object`: Conversation.ObjectType?): String {
+ override fun convertToString(`object`: ConversationEnums.ObjectType?): String {
if (`object` == null) {
return ""
}
return when (`object`) {
- Conversation.ObjectType.SHARE_PASSWORD -> "share:password"
- Conversation.ObjectType.ROOM -> "room"
- Conversation.ObjectType.FILE -> "file"
+ ConversationEnums.ObjectType.SHARE_PASSWORD -> "share:password"
+ ConversationEnums.ObjectType.ROOM -> "room"
+ ConversationEnums.ObjectType.FILE -> "file"
else -> ""
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumLobbyStateConverter.java b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumLobbyStateConverter.java
index efe9f88695..51f78ce434 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumLobbyStateConverter.java
+++ b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumLobbyStateConverter.java
@@ -8,22 +8,23 @@
import com.bluelinelabs.logansquare.typeconverters.IntBasedTypeConverter;
import com.nextcloud.talk.models.json.conversations.Conversation;
+import com.nextcloud.talk.models.json.conversations.ConversationEnums;
-public class EnumLobbyStateConverter extends IntBasedTypeConverter {
+public class EnumLobbyStateConverter extends IntBasedTypeConverter {
@Override
- public Conversation.LobbyState getFromInt(int i) {
+ public ConversationEnums.LobbyState getFromInt(int i) {
switch (i) {
case 0:
- return Conversation.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS;
+ return ConversationEnums.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS;
case 1:
- return Conversation.LobbyState.LOBBY_STATE_MODERATORS_ONLY;
+ return ConversationEnums.LobbyState.LOBBY_STATE_MODERATORS_ONLY;
default:
- return Conversation.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS;
+ return ConversationEnums.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS;
}
}
@Override
- public int convertToInt(Conversation.LobbyState object) {
+ public int convertToInt(ConversationEnums.LobbyState object) {
switch (object) {
case LOBBY_STATE_ALL_PARTICIPANTS:
return 0;
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumNotificationLevelConverter.java b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumNotificationLevelConverter.java
index 96d425a4de..e38bcc6974 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumNotificationLevelConverter.java
+++ b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumNotificationLevelConverter.java
@@ -8,26 +8,27 @@
import com.bluelinelabs.logansquare.typeconverters.IntBasedTypeConverter;
import com.nextcloud.talk.models.json.conversations.Conversation;
+import com.nextcloud.talk.models.json.conversations.ConversationEnums;
-public class EnumNotificationLevelConverter extends IntBasedTypeConverter {
+public class EnumNotificationLevelConverter extends IntBasedTypeConverter {
@Override
- public Conversation.NotificationLevel getFromInt(int i) {
+ public ConversationEnums.NotificationLevel getFromInt(int i) {
switch (i) {
case 0:
- return Conversation.NotificationLevel.DEFAULT;
+ return ConversationEnums.NotificationLevel.DEFAULT;
case 1:
- return Conversation.NotificationLevel.ALWAYS;
+ return ConversationEnums.NotificationLevel.ALWAYS;
case 2:
- return Conversation.NotificationLevel.MENTION;
+ return ConversationEnums.NotificationLevel.MENTION;
case 3:
- return Conversation.NotificationLevel.NEVER;
+ return ConversationEnums.NotificationLevel.NEVER;
default:
- return Conversation.NotificationLevel.DEFAULT;
+ return ConversationEnums.NotificationLevel.DEFAULT;
}
}
@Override
- public int convertToInt(Conversation.NotificationLevel object) {
+ public int convertToInt(ConversationEnums.NotificationLevel object) {
switch (object) {
case DEFAULT:
return 0;
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumReadOnlyConversationConverter.java b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumReadOnlyConversationConverter.java
index 3d20a8eaf6..ba76f71f8f 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumReadOnlyConversationConverter.java
+++ b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumReadOnlyConversationConverter.java
@@ -8,22 +8,23 @@
import com.bluelinelabs.logansquare.typeconverters.IntBasedTypeConverter;
import com.nextcloud.talk.models.json.conversations.Conversation;
+import com.nextcloud.talk.models.json.conversations.ConversationEnums;
-public class EnumReadOnlyConversationConverter extends IntBasedTypeConverter {
+public class EnumReadOnlyConversationConverter extends IntBasedTypeConverter {
@Override
- public Conversation.ConversationReadOnlyState getFromInt(int i) {
+ public ConversationEnums.ConversationReadOnlyState getFromInt(int i) {
switch (i) {
case 0:
- return Conversation.ConversationReadOnlyState.CONVERSATION_READ_WRITE;
+ return ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_WRITE;
case 1:
- return Conversation.ConversationReadOnlyState.CONVERSATION_READ_ONLY;
+ return ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_ONLY;
default:
- return Conversation.ConversationReadOnlyState.CONVERSATION_READ_WRITE;
+ return ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_WRITE;
}
}
@Override
- public int convertToInt(Conversation.ConversationReadOnlyState object) {
+ public int convertToInt(ConversationEnums.ConversationReadOnlyState object) {
switch (object) {
case CONVERSATION_READ_WRITE:
return 0;
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumRoomTypeConverter.java b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumRoomTypeConverter.java
index 37e75b2608..702e0a6fa6 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumRoomTypeConverter.java
+++ b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumRoomTypeConverter.java
@@ -7,31 +7,31 @@
package com.nextcloud.talk.models.json.converters;
import com.bluelinelabs.logansquare.typeconverters.IntBasedTypeConverter;
-import com.nextcloud.talk.models.json.conversations.Conversation;
+import com.nextcloud.talk.models.json.conversations.ConversationEnums;
-public class EnumRoomTypeConverter extends IntBasedTypeConverter {
+public class EnumRoomTypeConverter extends IntBasedTypeConverter {
@Override
- public Conversation.ConversationType getFromInt(int i) {
+ public ConversationEnums.ConversationType getFromInt(int i) {
switch (i) {
case 1:
- return Conversation.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL;
+ return ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL;
case 2:
- return Conversation.ConversationType.ROOM_GROUP_CALL;
+ return ConversationEnums.ConversationType.ROOM_GROUP_CALL;
case 3:
- return Conversation.ConversationType.ROOM_PUBLIC_CALL;
+ return ConversationEnums.ConversationType.ROOM_PUBLIC_CALL;
case 4:
- return Conversation.ConversationType.ROOM_SYSTEM;
+ return ConversationEnums.ConversationType.ROOM_SYSTEM;
case 5:
- return Conversation.ConversationType.FORMER_ONE_TO_ONE;
+ return ConversationEnums.ConversationType.FORMER_ONE_TO_ONE;
case 6:
- return Conversation.ConversationType.NOTE_TO_SELF;
+ return ConversationEnums.ConversationType.NOTE_TO_SELF;
default:
- return Conversation.ConversationType.DUMMY;
+ return ConversationEnums.ConversationType.DUMMY;
}
}
@Override
- public int convertToInt(Conversation.ConversationType object) {
+ public int convertToInt(ConversationEnums.ConversationType object) {
switch (object) {
case DUMMY:
return 0;
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt
index cb4091d17c..6cc84fe2b9 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt
@@ -9,66 +9,66 @@
package com.nextcloud.talk.models.json.converters
import com.bluelinelabs.logansquare.typeconverters.StringBasedTypeConverter
-import com.nextcloud.talk.models.json.chat.ChatMessage
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.AUDIO_RECORDING_STARTED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.AUDIO_RECORDING_STOPPED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.AVATAR_REMOVED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.AVATAR_SET
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.BREAKOUT_ROOMS_STARTED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.BREAKOUT_ROOMS_STOPPED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CALL_ENDED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CALL_ENDED_EVERYONE
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CALL_JOINED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CALL_LEFT
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CALL_MISSED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CALL_STARTED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CALL_TRIED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CIRCLE_ADDED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CIRCLE_REMOVED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CLEARED_CHAT
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CONVERSATION_CREATED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.CONVERSATION_RENAMED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.DESCRIPTION_REMOVED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.DESCRIPTION_SET
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.DUMMY
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.FILE_SHARED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.GROUP_ADDED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.GROUP_REMOVED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.GUESTS_ALLOWED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.GUESTS_DISALLOWED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.GUEST_MODERATOR_DEMOTED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.GUEST_MODERATOR_PROMOTED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.LISTABLE_ALL
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.LISTABLE_NONE
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.LISTABLE_USERS
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.LOBBY_NONE
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.LOBBY_NON_MODERATORS
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.LOBBY_OPEN_TO_EVERYONE
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MATTERBRIDGE_CONFIG_ADDED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MATTERBRIDGE_CONFIG_DISABLED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MATTERBRIDGE_CONFIG_EDITED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MATTERBRIDGE_CONFIG_ENABLED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MATTERBRIDGE_CONFIG_REMOVED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MESSAGE_DELETED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MESSAGE_EXPIRATION_DISABLED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MESSAGE_EXPIRATION_ENABLED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MODERATOR_DEMOTED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.MODERATOR_PROMOTED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.OBJECT_SHARED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_REMOVED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.PASSWORD_SET
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.POLL_CLOSED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.POLL_VOTED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION_DELETED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION_REVOKED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.READ_ONLY
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.READ_ONLY_OFF
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.RECORDING_FAILED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.RECORDING_STARTED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.RECORDING_STOPPED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.USER_ADDED
-import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.USER_REMOVED
+import com.nextcloud.talk.chat.data.model.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.AUDIO_RECORDING_STARTED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.AUDIO_RECORDING_STOPPED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.AVATAR_REMOVED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.AVATAR_SET
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.BREAKOUT_ROOMS_STARTED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.BREAKOUT_ROOMS_STOPPED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.CALL_ENDED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.CALL_ENDED_EVERYONE
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.CALL_JOINED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.CALL_LEFT
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.CALL_MISSED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.CALL_STARTED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.CALL_TRIED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.CIRCLE_ADDED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.CIRCLE_REMOVED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.CLEARED_CHAT
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.CONVERSATION_CREATED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.CONVERSATION_RENAMED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.DESCRIPTION_REMOVED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.DESCRIPTION_SET
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.DUMMY
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.FILE_SHARED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.GROUP_ADDED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.GROUP_REMOVED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.GUESTS_ALLOWED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.GUESTS_DISALLOWED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.GUEST_MODERATOR_DEMOTED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.GUEST_MODERATOR_PROMOTED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.LISTABLE_ALL
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.LISTABLE_NONE
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.LISTABLE_USERS
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.LOBBY_NONE
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.LOBBY_NON_MODERATORS
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.LOBBY_OPEN_TO_EVERYONE
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.MATTERBRIDGE_CONFIG_ADDED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.MATTERBRIDGE_CONFIG_DISABLED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.MATTERBRIDGE_CONFIG_EDITED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.MATTERBRIDGE_CONFIG_ENABLED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.MATTERBRIDGE_CONFIG_REMOVED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.MESSAGE_DELETED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.MESSAGE_EXPIRATION_DISABLED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.MESSAGE_EXPIRATION_ENABLED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.MODERATOR_DEMOTED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.MODERATOR_PROMOTED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.OBJECT_SHARED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.PASSWORD_REMOVED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.PASSWORD_SET
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.POLL_CLOSED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.POLL_VOTED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.REACTION
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.REACTION_DELETED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.REACTION_REVOKED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.READ_ONLY
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.READ_ONLY_OFF
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.RECORDING_FAILED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.RECORDING_STARTED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.RECORDING_STOPPED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.USER_ADDED
+import com.nextcloud.talk.chat.data.model.ChatMessage.SystemMessageType.USER_REMOVED
/*
* see https://nextcloud-talk.readthedocs.io/en/latest/chat/#system-messages
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomPropertiesWebSocketMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomPropertiesWebSocketMessage.kt
index 7271d87424..d2cca2853b 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomPropertiesWebSocketMessage.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomPropertiesWebSocketMessage.kt
@@ -10,7 +10,7 @@ package com.nextcloud.talk.models.json.websocket
import android.os.Parcelable
import com.bluelinelabs.logansquare.annotation.JsonField
import com.bluelinelabs.logansquare.annotation.JsonObject
-import com.nextcloud.talk.models.json.conversations.Conversation.ConversationType
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.converters.EnumRoomTypeConverter
import kotlinx.parcelize.Parcelize
@@ -20,7 +20,7 @@ data class RoomPropertiesWebSocketMessage(
@JsonField(name = ["name"])
var name: String? = null,
@JsonField(name = ["type"], typeConverter = EnumRoomTypeConverter::class)
- var roomType: ConversationType? = null
+ var roomType: ConversationEnums.ConversationType? = null
) : Parcelable {
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
constructor() : this(null, null)
diff --git a/app/src/main/java/com/nextcloud/talk/openconversations/adapters/OpenConversationsAdapter.kt b/app/src/main/java/com/nextcloud/talk/openconversations/adapters/OpenConversationsAdapter.kt
index 645b49c5f0..2fdc128cc0 100644
--- a/app/src/main/java/com/nextcloud/talk/openconversations/adapters/OpenConversationsAdapter.kt
+++ b/app/src/main/java/com/nextcloud/talk/openconversations/adapters/OpenConversationsAdapter.kt
@@ -76,6 +76,6 @@ object ConversationsCallback : DiffUtil.ItemCallback() {
}
override fun areContentsTheSame(oldItem: OpenConversation, newItem: OpenConversation): Boolean {
- return oldItem.roomId == newItem.roomId
+ return oldItem.roomToken == newItem.roomToken
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/openconversations/data/OpenConversation.kt b/app/src/main/java/com/nextcloud/talk/openconversations/data/OpenConversation.kt
index 33918185da..65d272e512 100644
--- a/app/src/main/java/com/nextcloud/talk/openconversations/data/OpenConversation.kt
+++ b/app/src/main/java/com/nextcloud/talk/openconversations/data/OpenConversation.kt
@@ -7,7 +7,7 @@
package com.nextcloud.talk.openconversations.data
data class OpenConversation(
- var roomId: String,
+ // var roomId: String,
var roomToken: String,
var displayName: String,
var description: String?
diff --git a/app/src/main/java/com/nextcloud/talk/openconversations/data/OpenConversationsRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/openconversations/data/OpenConversationsRepositoryImpl.kt
index 156d0f4e56..5edbf922d6 100644
--- a/app/src/main/java/com/nextcloud/talk/openconversations/data/OpenConversationsRepositoryImpl.kt
+++ b/app/src/main/java/com/nextcloud/talk/openconversations/data/OpenConversationsRepositoryImpl.kt
@@ -32,7 +32,7 @@ class OpenConversationsRepositoryImpl(private val ncApi: NcApi, currentUserProvi
return OpenConversationsModel(
conversations.map { conversation ->
OpenConversation(
- conversation.roomId!!,
+ // conversation.roomId!!,
conversation.token!!,
conversation.name!!,
conversation.description ?: ""
diff --git a/app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepositoryImpl.kt
index 3142f3686c..4b89720964 100644
--- a/app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepositoryImpl.kt
+++ b/app/src/main/java/com/nextcloud/talk/repositories/conversations/ConversationsRepositoryImpl.kt
@@ -18,7 +18,10 @@ import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import io.reactivex.Observable
-class ConversationsRepositoryImpl(private val api: NcApi, private val userProvider: CurrentUserProviderNew) :
+class ConversationsRepositoryImpl(
+ private val api: NcApi,
+ private val userProvider: CurrentUserProviderNew
+) :
ConversationsRepository {
private val user: User
diff --git a/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepository.kt b/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepository.kt
index 0687118185..157df46983 100644
--- a/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepository.kt
+++ b/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepository.kt
@@ -8,7 +8,7 @@ package com.nextcloud.talk.repositories.reactions
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import io.reactivex.Observable
interface ReactionsRepository {
diff --git a/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepositoryImpl.kt
index e6d4e6c441..b84d36a4a6 100644
--- a/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepositoryImpl.kt
+++ b/app/src/main/java/com/nextcloud/talk/repositories/reactions/ReactionsRepositoryImpl.kt
@@ -7,17 +7,26 @@
package com.nextcloud.talk.repositories.reactions
import com.nextcloud.talk.api.NcApi
+import com.nextcloud.talk.chat.data.model.ChatMessage
+import com.nextcloud.talk.data.database.dao.ChatMessagesDao
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
-import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.generic.GenericMeta
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.user.CurrentUserProviderNew
import io.reactivex.Observable
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.launch
+import javax.inject.Inject
-class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: CurrentUserProviderNew) :
- ReactionsRepository {
+class ReactionsRepositoryImpl @Inject constructor(
+ private val ncApi: NcApi,
+ private val currentUserProvider: CurrentUserProviderNew,
+ private val dao: ChatMessagesDao
+) : ReactionsRepository {
val currentUser: User = currentUserProvider.currentUser.blockingGet()
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)!!
@@ -31,7 +40,11 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
message.id
),
emoji
- ).map { mapToReactionAddedModel(message, emoji, it.ocs?.meta!!) }
+ ).map {
+ val model = mapToReactionAddedModel(message, emoji, it.ocs?.meta!!)
+ persistAddedModel(model, roomToken)
+ return@map model
+ }
}
override fun deleteReaction(
@@ -47,7 +60,11 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
message.id
),
emoji
- ).map { mapToReactionDeletedModel(message, emoji, it.ocs?.meta!!) }
+ ).map {
+ val model = mapToReactionDeletedModel(message, emoji, it.ocs?.meta!!)
+ persistDeletedModel(model, roomToken)
+ return@map model
+ }
}
private fun mapToReactionAddedModel(
@@ -76,6 +93,66 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
)
}
+ private fun persistAddedModel(model: ReactionAddedModel, roomToken: String) =
+ CoroutineScope(Dispatchers.IO).launch {
+ // 1. Call DAO, Get a singular ChatMessageEntity with model.chatMessage.{PARAM}
+ val accountId = currentUser.id!!
+ val id = model.chatMessage.jsonMessageId.toLong()
+ val internalConversationId = "$accountId@$roomToken"
+ val emoji = model.emoji
+
+ val message = dao.getChatMessageForConversation(internalConversationId, id).first()
+
+ // 2. Check state of entity, create params as needed
+ if (message.reactions == null) {
+ message.reactions = LinkedHashMap()
+ }
+
+ if (message.reactionsSelf == null) {
+ message.reactionsSelf = ArrayList()
+ }
+
+ var amount = message.reactions!![emoji]
+ if (amount == null) {
+ amount = 0
+ }
+ message.reactions!![emoji] = amount + 1
+ message.reactionsSelf!!.add(emoji)
+
+ // 3. Call DAO again, to update the singular ChatMessageEntity with params
+ dao.updateChatMessage(message)
+ }
+
+ private fun persistDeletedModel(model: ReactionDeletedModel, roomToken: String) =
+ CoroutineScope(Dispatchers.IO).launch {
+ // 1. Call DAO, Get a singular ChatMessageEntity with model.chatMessage.{PARAM}
+ val accountId = currentUser.id!!
+ val id = model.chatMessage.jsonMessageId.toLong()
+ val internalConversationId = "$accountId@$roomToken"
+ val emoji = model.emoji
+
+ val message = dao.getChatMessageForConversation(internalConversationId, id).first()
+
+ // 2. Check state of entity, create params as needed
+ if (message.reactions == null) {
+ message.reactions = LinkedHashMap()
+ }
+
+ if (message.reactionsSelf == null) {
+ message.reactionsSelf = ArrayList()
+ }
+
+ var amount = message.reactions!![emoji]
+ if (amount == null) {
+ amount = 0
+ }
+ message.reactions!![emoji] = amount - 1
+ message.reactionsSelf!!.remove(emoji)
+
+ // 3. Call DAO again, to update the singular ChatMessageEntity with params
+ dao.updateChatMessage(message)
+ }
+
companion object {
private const val HTTP_OK: Int = 200
private const val HTTP_CREATED: Int = 201
diff --git a/app/src/main/java/com/nextcloud/talk/shareditems/repositories/SharedItemsRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/shareditems/repositories/SharedItemsRepositoryImpl.kt
index 28475518db..d306aec837 100644
--- a/app/src/main/java/com/nextcloud/talk/shareditems/repositories/SharedItemsRepositoryImpl.kt
+++ b/app/src/main/java/com/nextcloud/talk/shareditems/repositories/SharedItemsRepositoryImpl.kt
@@ -26,7 +26,6 @@ import com.nextcloud.talk.utils.DateConstants
import com.nextcloud.talk.utils.DateUtils
import io.reactivex.Observable
import retrofit2.Response
-import java.util.HashMap
import java.util.Locale
import javax.inject.Inject
@@ -79,7 +78,7 @@ class SharedItemsRepositoryImpl @Inject constructor(private val ncApi: NcApi, pr
val previewAvailable =
"yes".equals(fileParameters["preview-available"]!!, ignoreCase = true)
- items[it.value.id] = SharedFileItem(
+ items[it.value.id.toString()] = SharedFileItem(
fileParameters["id"]!!,
fileParameters["name"]!!,
actorParameters["id"]!!,
@@ -94,7 +93,7 @@ class SharedItemsRepositoryImpl @Inject constructor(private val ncApi: NcApi, pr
)
} else if (it.value.messageParameters?.containsKey("object") == true) {
val objectParameters = it.value.messageParameters!!["object"]!!
- items[it.value.id] = itemFromObject(objectParameters, actorParameters, dateTime)
+ items[it.value.id.toString()] = itemFromObject(objectParameters, actorParameters, dateTime)
} else {
Log.w(TAG, "Item contains neither 'file' or 'object'.")
}
diff --git a/app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt b/app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt
index 1204d7ebb5..5684e9ff11 100644
--- a/app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt
+++ b/app/src/main/java/com/nextcloud/talk/ui/bottom/sheet/ProfileBottomSheet.kt
@@ -21,7 +21,7 @@ import com.nextcloud.talk.chat.ChatActivity
import com.nextcloud.talk.bottomsheet.items.BasicListItemWithImage
import com.nextcloud.talk.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.data.user.model.User
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.hovercard.HoverCardAction
import com.nextcloud.talk.models.json.hovercard.HoverCardOverall
@@ -140,7 +140,7 @@ class ProfileBottomSheet(val ncApi: NcApi, val userModel: User, val viewThemeUti
override fun onNext(roomOverall: RoomOverall) {
val bundle = Bundle()
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, roomOverall.ocs!!.data!!.token)
- bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
+ // bundle.putString(BundleKeys.KEY_ROOM_ID, roomOverall.ocs!!.data!!.roomId)
val chatIntent = Intent(context, ChatActivity::class.java)
chatIntent.putExtras(bundle)
diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt
index a5f8bdbd5d..1ca4054ca1 100644
--- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt
+++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/ConversationsListBottomDialog.kt
@@ -25,12 +25,13 @@ import com.nextcloud.talk.conversationlist.ConversationsListActivity
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.DialogConversationOperationsBinding
import com.nextcloud.talk.jobs.LeaveConversationWorker
-import com.nextcloud.talk.models.json.conversations.Conversation
+import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.users.UserManager
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.CapabilitiesUtil
+import com.nextcloud.talk.utils.ConversationUtils
import com.nextcloud.talk.utils.ShareUtils
import com.nextcloud.talk.utils.SpreedFeatures
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
@@ -45,7 +46,7 @@ import javax.inject.Inject
class ConversationsListBottomDialog(
val activity: ConversationsListActivity,
val currentUser: User,
- val conversation: Conversation
+ val conversation: ConversationModel
) : BottomSheetDialog(activity) {
private lateinit var binding: DialogConversationOperationsBinding
@@ -98,7 +99,7 @@ class ConversationsListBottomDialog(
currentUser.capabilities?.spreedCapability!!,
SpreedFeatures.FAVORITES
)
- val canModerate = conversation.canModerate(currentUser)
+ val canModerate = ConversationUtils.canModerate(conversation, currentUser.capabilities?.spreedCapability!!)
binding.conversationRemoveFromFavorites.visibility = setVisibleIf(
hasFavoritesCapability && conversation.favorite
@@ -122,10 +123,10 @@ class ConversationsListBottomDialog(
)
binding.conversationOperationRename.visibility = setVisibleIf(
- conversation.isNameEditable(currentUser)
+ ConversationUtils.isNameEditable(conversation, currentUser.capabilities!!.spreedCapability!!)
)
binding.conversationLinkShare.visibility = setVisibleIf(
- !conversation.isNoteToSelfConversation()
+ !ConversationUtils.isNoteToSelfConversation(conversation)
)
binding.conversationOperationDelete.visibility = setVisibleIf(
@@ -133,10 +134,10 @@ class ConversationsListBottomDialog(
)
binding.conversationOperationLeave.visibility = setVisibleIf(
- conversation.canLeave() &&
+ ConversationUtils.canLeave(conversation) &&
// leaving is by api not possible for the last user with moderator permissions.
// for now, hide this option for all moderators.
- !conversation.canModerate(currentUser)
+ !ConversationUtils.canModerate(conversation, currentUser.capabilities!!.spreedCapability!!)
)
}
@@ -311,7 +312,7 @@ class ConversationsListBottomDialog(
private fun markConversationAsRead() {
val messageId = if (conversation.remoteServer.isNullOrEmpty()) {
- conversation.lastMessage!!.jsonMessageId
+ conversation.lastMessage?.id
} else {
null
}
@@ -323,7 +324,7 @@ class ConversationsListBottomDialog(
currentUser.baseUrl!!,
conversation.token!!
),
- messageId
+ messageId?.toInt()
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt
index 7fddcf2e94..55f40854fd 100644
--- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt
+++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MessageActionsDialog.kt
@@ -23,16 +23,15 @@ 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.chat.viewmodels.ChatViewModel
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.DialogMessageActionsBinding
import com.nextcloud.talk.models.domain.ConversationModel
-import com.nextcloud.talk.models.domain.ConversationReadOnlyState
-import com.nextcloud.talk.models.domain.ConversationType
import com.nextcloud.talk.models.domain.ReactionAddedModel
import com.nextcloud.talk.models.domain.ReactionDeletedModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.repositories.reactions.ReactionsRepository
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.ApiUtils
@@ -89,7 +88,7 @@ class MessageActionsDialog(
private val isUserAllowedToEdit = chatActivity.userAllowedByPrivilages(message)
- private val isMessageEditable = CapabilitiesUtil.hasSpreedFeatureCapability(
+ private val isMessageEditable = hasSpreedFeatureCapability(
spreedCapabilities,
SpreedFeatures.EDIT_MESSAGES
) && messageHasRegularText && !isOlderThanTwentyFourHours && isUserAllowedToEdit
@@ -108,7 +107,7 @@ class MessageActionsDialog(
initMenuItemCopy(!message.isDeleted)
val apiVersion = ApiUtils.getConversationApiVersion(user!!, intArrayOf(ApiUtils.API_V4, ApiUtils.API_V3, 1))
chatActivity.chatViewModel.checkForNoteToSelf(
- ApiUtils.getCredentials(user!!.username, user.token)!!,
+ ApiUtils.getCredentials(user.username, user.token)!!,
ApiUtils.getUrlForRooms(
apiVersion,
user.baseUrl
@@ -136,13 +135,13 @@ class MessageActionsDialog(
ChatMessage.MessageType.REGULAR_TEXT_MESSAGE == message.getCalculateMessageType() &&
CapabilitiesUtil.isTranslationsSupported(spreedCapabilities)
)
- initMenuEditorDetails(message.lastEditTimestamp != 0L && !message.isDeleted)
+ initMenuEditorDetails(message.lastEditTimestamp!! != 0L && !message.isDeleted)
initMenuReplyToMessage(message.replyable && hasChatPermission)
initMenuReplyPrivately(
message.replyable &&
hasUserId(user) &&
hasUserActorId(message) &&
- currentConversation?.type != ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
+ currentConversation?.type != ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
)
initMenuEditMessage(isMessageEditable)
initMenuDeleteMessage(showMessageDeletionButton)
@@ -276,7 +275,7 @@ class MessageActionsDialog(
}
private fun isPermitted(hasChatPermission: Boolean): Boolean {
- return hasChatPermission && ConversationReadOnlyState.CONVERSATION_READ_ONLY !=
+ return hasChatPermission && ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_ONLY !=
currentConversation?.conversationReadOnlyState
}
@@ -367,7 +366,7 @@ class MessageActionsDialog(
private fun initMenuEditorDetails(showEditorDetails: Boolean) {
if (showEditorDetails) {
val editedTime = dateUtils.getLocalDateTimeStringFromTimestamp(
- message.lastEditTimestamp *
+ message.lastEditTimestamp!! *
DateConstants.SECOND_DIVIDER
)
val lastEditorName = message.lastEditActorDisplayName ?: ""
diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt
index e87c2e6fb8..8020ffdf0c 100644
--- a/app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt
+++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/ShowReactionsDialog.kt
@@ -25,10 +25,11 @@ import com.nextcloud.talk.adapters.ReactionItemClickListener
import com.nextcloud.talk.adapters.ReactionsAdapter
import com.nextcloud.talk.api.NcApi
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.user.model.User
import com.nextcloud.talk.databinding.DialogMessageReactionsBinding
import com.nextcloud.talk.databinding.ItemReactionsTabBinding
-import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.models.json.reactions.ReactionsOverall
import com.nextcloud.talk.ui.theme.ViewThemeUtils
@@ -42,7 +43,7 @@ import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class ShowReactionsDialog(
- activity: Activity,
+ val activity: Activity,
private val roomToken: String,
private val chatMessage: ChatMessage,
private val user: User?,
@@ -86,7 +87,7 @@ class ShowReactionsDialog(
if (chatMessage.reactions != null && chatMessage.reactions!!.isNotEmpty()) {
var reactionsTotal = 0
for ((emoji, amount) in chatMessage.reactions!!) {
- reactionsTotal = reactionsTotal.plus(amount as Int)
+ reactionsTotal = reactionsTotal.plus(amount)
val tab: TabLayout.Tab = binding.emojiReactionsTabs.newTab() // Create a new Tab names "First Tab"
val itemBinding = ItemReactionsTabBinding.inflate(layoutInflater)
@@ -163,7 +164,7 @@ class ShowReactionsDialog(
}
}
- Collections.sort(reactionVoters, ReactionComparator(user?.userId))
+ Collections.sort(reactionVoters, ReactionComparator(user.userId))
adapter?.list?.addAll(reactionVoters)
adapter?.notifyDataSetChanged()
@@ -185,13 +186,13 @@ class ShowReactionsDialog(
override fun onClick(reactionItem: ReactionItem) {
if (hasChatPermission && reactionItem.reactionVoter.actorId?.equals(user?.userId) == true) {
deleteReaction(chatMessage, reactionItem.reaction!!)
+ adapter?.list?.remove(reactionItem)
dismiss()
}
}
private fun deleteReaction(message: ChatMessage, emoji: String) {
val credentials = ApiUtils.getCredentials(user?.username, user?.token)
-
ncApi.deleteReaction(
credentials,
ApiUtils.getUrlForMessageReaction(
@@ -210,6 +211,7 @@ class ShowReactionsDialog(
override fun onNext(genericOverall: GenericOverall) {
Log.d(TAG, "deleted reaction: $emoji")
+ (activity as ChatActivity).updateUiToDeleteReaction(message, emoji)
}
override fun onError(e: Throwable) {
diff --git a/app/src/main/java/com/nextcloud/talk/utils/ConversationUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/ConversationUtils.kt
index 623c0d9b9e..793ed6eff5 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/ConversationUtils.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/ConversationUtils.kt
@@ -7,52 +7,52 @@
package com.nextcloud.talk.utils
import com.nextcloud.talk.models.domain.ConversationModel
-import com.nextcloud.talk.models.domain.ConversationType
-import com.nextcloud.talk.models.domain.ParticipantType
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
+import com.nextcloud.talk.models.json.conversations.ConversationEnums
+import com.nextcloud.talk.models.json.participants.Participant
object ConversationUtils {
private val TAG = ConversationUtils::class.java.simpleName
fun isPublic(conversation: ConversationModel): Boolean {
- return ConversationType.ROOM_PUBLIC_CALL == conversation.type
+ return ConversationEnums.ConversationType.ROOM_PUBLIC_CALL == conversation.type
}
fun isGuest(conversation: ConversationModel): Boolean {
- return ParticipantType.GUEST == conversation.participantType ||
- ParticipantType.GUEST_MODERATOR == conversation.participantType ||
- ParticipantType.USER_FOLLOWING_LINK == conversation.participantType
+ return Participant.ParticipantType.GUEST == conversation.participantType ||
+ Participant.ParticipantType.GUEST_MODERATOR == conversation.participantType ||
+ Participant.ParticipantType.USER_FOLLOWING_LINK == conversation.participantType
}
fun isParticipantOwnerOrModerator(conversation: ConversationModel): Boolean {
- return ParticipantType.OWNER == conversation.participantType ||
- ParticipantType.GUEST_MODERATOR == conversation.participantType ||
- ParticipantType.MODERATOR == conversation.participantType
+ return Participant.ParticipantType.OWNER == conversation.participantType ||
+ Participant.ParticipantType.GUEST_MODERATOR == conversation.participantType ||
+ Participant.ParticipantType.MODERATOR == conversation.participantType
}
fun isLockedOneToOne(conversation: ConversationModel, spreedCapabilities: SpreedCapability): Boolean {
- return conversation.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
+ return conversation.type == ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL &&
CapabilitiesUtil.hasSpreedFeatureCapability(spreedCapabilities, SpreedFeatures.LOCKED_ONE_TO_ONE)
}
fun canModerate(conversation: ConversationModel, spreedCapabilities: SpreedCapability): Boolean {
return isParticipantOwnerOrModerator(conversation) &&
!isLockedOneToOne(conversation, spreedCapabilities) &&
- conversation.type != ConversationType.FORMER_ONE_TO_ONE &&
+ conversation.type != ConversationEnums.ConversationType.FORMER_ONE_TO_ONE &&
!isNoteToSelfConversation(conversation)
}
fun isLobbyViewApplicable(conversation: ConversationModel, spreedCapabilities: SpreedCapability): Boolean {
return !canModerate(conversation, spreedCapabilities) &&
(
- conversation.type == ConversationType.ROOM_GROUP_CALL ||
- conversation.type == ConversationType.ROOM_PUBLIC_CALL
+ conversation.type == ConversationEnums.ConversationType.ROOM_GROUP_CALL ||
+ conversation.type == ConversationEnums.ConversationType.ROOM_PUBLIC_CALL
)
}
fun isNameEditable(conversation: ConversationModel, spreedCapabilities: SpreedCapability): Boolean {
return canModerate(conversation, spreedCapabilities) &&
- ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != conversation.type
+ ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL != conversation.type
}
fun canLeave(conversation: ConversationModel): Boolean {
@@ -75,6 +75,7 @@ object ConversationUtils {
}
fun isNoteToSelfConversation(currentConversation: ConversationModel?): Boolean {
- return currentConversation != null && currentConversation.type == ConversationType.NOTE_TO_SELF
+ return currentConversation != null &&
+ currentConversation.type == ConversationEnums.ConversationType.NOTE_TO_SELF
}
}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt
index f5c1e33c94..fac47d5c6a 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/FileViewerUtils.kt
@@ -29,7 +29,7 @@ import com.nextcloud.talk.fullscreenfile.FullScreenTextViewerActivity
import com.nextcloud.talk.adapters.messages.PreviewMessageViewHolder
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.utils.AccountUtils.canWeOpenFilesApp
import com.nextcloud.talk.utils.Mimetype.AUDIO_MPEG
import com.nextcloud.talk.utils.Mimetype.AUDIO_OGG
@@ -128,7 +128,7 @@ class FileViewerUtils(private val context: Context, private val user: User) {
) {
val file = File(context.cacheDir, fileInfo.fileName)
if (file.exists()) {
- openFileByMimetype(fileInfo.fileName!!, mimetype)
+ openFileByMimetype(fileInfo.fileName, mimetype)
} else {
downloadFileToCache(
fileInfo,
@@ -267,7 +267,7 @@ class FileViewerUtils(private val context: Context, private val user: User) {
openWhenDownloaded: Boolean
) {
// check if download worker is already running
- val workers = WorkManager.getInstance(context).getWorkInfosByTag(fileInfo.fileId!!)
+ val workers = WorkManager.getInstance(context).getWorkInfosByTag(fileInfo.fileId)
try {
for (workInfo in workers.get()) {
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
diff --git a/app/src/main/java/com/nextcloud/talk/utils/ParticipantPermissions.kt b/app/src/main/java/com/nextcloud/talk/utils/ParticipantPermissions.kt
index 05233f1f5b..32ec0f48d1 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/ParticipantPermissions.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/ParticipantPermissions.kt
@@ -9,7 +9,6 @@ package com.nextcloud.talk.utils
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
-import com.nextcloud.talk.models.json.conversations.Conversation
/**
* see https://nextcloud-talk.readthedocs.io/en/latest/constants/#attendee-permissions
@@ -18,13 +17,6 @@ class ParticipantPermissions(
private val spreedCapabilities: SpreedCapability,
private val conversation: ConversationModel
) {
-
- @Deprecated("Use ChatRepository.ConversationModel")
- constructor(spreedCapabilities: SpreedCapability, conversation: Conversation) : this(
- spreedCapabilities,
- ConversationModel.mapToConversationModel(conversation)
- )
-
val isDefault = (conversation.permissions and DEFAULT) == DEFAULT
val isCustom = (conversation.permissions and CUSTOM) == CUSTOM
private val canStartCall = (conversation.permissions and START_CALL) == START_CALL
diff --git a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
index 50e8c28fd3..53de01b275 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt
@@ -77,4 +77,7 @@ object BundleKeys {
const val KEY_REMOTE_TALK_SHARE = "KEY_REMOTE_TALK_SHARE"
const val KEY_CHAT_API_VERSION = "KEY_CHAT_API_VERSION"
const val KEY_CALL_FLAG = "KEY_CALL_FLAG"
+ const val KEY_CREDENTIALS: String = "KEY_CREDENTIALS"
+ const val KEY_FIELD_MAP: String = "KEY_FIELD_MAP"
+ const val KEY_CHAT_URL: String = "KEY_CHAT_URL"
}
diff --git a/app/src/main/java/com/nextcloud/talk/utils/message/MessageUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/message/MessageUtils.kt
index 9ac61bb0aa..fa3a3b93c8 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/message/MessageUtils.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/message/MessageUtils.kt
@@ -14,7 +14,7 @@ import android.text.Spanned
import android.util.Log
import android.view.View
import com.nextcloud.talk.R
-import com.nextcloud.talk.models.json.chat.ChatMessage
+import com.nextcloud.talk.chat.data.model.ChatMessage
import com.nextcloud.talk.ui.theme.ViewThemeUtils
import com.nextcloud.talk.utils.DisplayUtils
import io.noties.markwon.AbstractMarkwonPlugin
diff --git a/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.kt
index e678ada948..d03033fba5 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.kt
+++ b/app/src/main/java/com/nextcloud/talk/utils/power/PowerManagerUtils.kt
@@ -40,7 +40,7 @@ class PowerManagerUtils {
init {
sharedApplication!!.componentApplication.inject(this)
- val pm = context!!.getSystemService(Context.POWER_SERVICE) as PowerManager
+ val pm = context!!.getSystemService(POWER_SERVICE) as PowerManager
fullLock = pm.newWakeLock(
PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
"nctalk:fullwakelock"
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 12683ac50d..1769aa23d0 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
@@ -10,6 +10,10 @@
import android.annotation.SuppressLint;
+import com.nextcloud.talk.chat.viewmodels.MessageInputViewModel;
+
+import java.util.List;
+
@SuppressLint("NonConstantResourceId")
public interface AppPreferences {
@@ -164,5 +168,14 @@ public interface AppPreferences {
Float[] getWaveFormFromFile(String filename);
+ void saveLastKnownId(String internalConversationId, int lastReadId);
+
+ int getLastKnownId(String internalConversationId, int defaultValue);
+
+ void saveMessageQueue(String internalConversationId, List queue);
+
+ List getMessageQueue(String internalConversationId);
+
+
void clear();
}
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 df45c5e1f3..849755d8bf 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
@@ -7,6 +7,7 @@
package com.nextcloud.talk.utils.preferences
import android.content.Context
+import android.util.Log
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
@@ -15,6 +16,7 @@ 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 kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.Flow
@@ -460,6 +462,62 @@ class AppPreferencesImpl(val context: Context) : AppPreferences {
return if (string.isNotEmpty()) string.convertStringToArray() else floatArrayOf().toTypedArray()
}
+ override fun saveLastKnownId(internalConversationId: String, lastReadId: Int) {
+ runBlocking {
+ async {
+ writeString(internalConversationId, lastReadId.toString())
+ }
+ }
+ }
+
+ override fun getLastKnownId(internalConversationId: String, defaultValue: Int): Int {
+ val lastReadId = runBlocking { async { readString(internalConversationId).first() } }.getCompleted()
+ 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.message},${msg.replyTo},${msg.displayName},${msg.sendWithoutNotification}]"
+ queueStr += msgStr
+ }
+ }
+ writeString(internalConversationId + MESSAGE_QUEUE, queueStr)
+ }
+ }
+ }
+
+ 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 {
+ val msgArray = msgStr.replace("[", "").split(",")
+ val message = msgArray[0]
+ val replyTo = msgArray[1].toInt()
+ val displayName = msgArray[2]
+ val silent = msgArray[3].toBoolean()
+
+ val qMsg = MessageInputViewModel.QueuedMessage(message, displayName, replyTo, silent)
+ queue.add(qMsg)
+ } catch (e: IndexOutOfBoundsException) {
+ Log.e(TAG, "Message string: $msgStr\n $e")
+ }
+ }
+
+ return queue
+ }
+
override fun clear() {}
private suspend fun writeString(key: String, value: String) =
@@ -538,6 +596,7 @@ 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"
private fun String.convertStringToArray(): Array {
var varString = this
val floatList = mutableListOf()
diff --git a/app/src/main/java/com/nextcloud/talk/utils/singletons/ApplicationWideCurrentRoomHolder.java b/app/src/main/java/com/nextcloud/talk/utils/singletons/ApplicationWideCurrentRoomHolder.java
index 3d8039e65a..515bae0633 100644
--- a/app/src/main/java/com/nextcloud/talk/utils/singletons/ApplicationWideCurrentRoomHolder.java
+++ b/app/src/main/java/com/nextcloud/talk/utils/singletons/ApplicationWideCurrentRoomHolder.java
@@ -14,7 +14,7 @@ public class ApplicationWideCurrentRoomHolder {
public static final String TAG = "ApplicationWideCurrentRoomHolder";
private static final ApplicationWideCurrentRoomHolder holder = new ApplicationWideCurrentRoomHolder();
- private String currentRoomId = "";
+// private String currentRoomId = "";
private String currentRoomToken = "";
private User userInRoom = new User();
private boolean inCall = false;
@@ -29,7 +29,7 @@ public static ApplicationWideCurrentRoomHolder getInstance() {
public void clear() {
Log.d(TAG, "ApplicationWideCurrentRoomHolder was cleared");
- currentRoomId = "";
+// currentRoomId = "";
userInRoom = new User();
inCall = false;
isDialing = false;
@@ -45,13 +45,13 @@ public void setCurrentRoomToken(String currentRoomToken) {
this.currentRoomToken = currentRoomToken;
}
- public String getCurrentRoomId() {
- return currentRoomId;
- }
-
- public void setCurrentRoomId(String currentRoomId) {
- this.currentRoomId = currentRoomId;
- }
+// public String getCurrentRoomId() {
+// return currentRoomId;
+// }
+//
+// public void setCurrentRoomId(String currentRoomId) {
+// this.currentRoomId = currentRoomId;
+// }
public User getUserInRoom() {
return userInRoom;
diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml
index ee60f4fd4e..b73e404d93 100644
--- a/app/src/main/res/layout/activity_chat.xml
+++ b/app/src/main/res/layout/activity_chat.xml
@@ -158,7 +158,7 @@
tools:visibility="visible" />
+
+
+
+
Ban participant
Show banned participants
Bans list
+ Connection lost - Sent messages are queued
+ Connection gained
+ Message deleted by you
Unban
Internal note
Ban
Show ban reason
Error occurred when unbanning participant
+ Connection Lost
diff --git a/app/src/test/java/com/nextcloud/talk/utils/ParticipantPermissionsTest.kt b/app/src/test/java/com/nextcloud/talk/utils/ParticipantPermissionsTest.kt
index be75924ce2..2b4ab0b395 100644
--- a/app/src/test/java/com/nextcloud/talk/utils/ParticipantPermissionsTest.kt
+++ b/app/src/test/java/com/nextcloud/talk/utils/ParticipantPermissionsTest.kt
@@ -17,7 +17,7 @@ class ParticipantPermissionsTest : TestCase() {
@Test
fun test_areFlagsSet() {
val spreedCapability = SpreedCapability()
- val conversation = Conversation()
+ val conversation = Conversation(null, null)
conversation.permissions = ParticipantPermissions.PUBLISH_SCREEN or
ParticipantPermissions.JOIN_CALL or
ParticipantPermissions.DEFAULT
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index a18342eed8..e89bbbfc87 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -255,7 +255,10 @@
+
+
+
@@ -1045,6 +1048,9 @@
+
+
+
@@ -1669,7 +1675,18 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -1682,6 +1699,14 @@
+
+
+
+
+
+
+
+
@@ -1690,6 +1715,14 @@
+
+
+
+
+
+
+
+
@@ -1738,6 +1771,14 @@
+
+
+
+
+
+
+
+
@@ -1754,6 +1795,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1762,6 +1819,14 @@
+
+
+
+
+
+
+
+