Offline support for conversations and chats
Authors: Julius Linus and Marcel Hibbe Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Родитель
b15c1787c2
Коммит
2408d639e4
|
@ -39,5 +39,6 @@
|
|||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||
<inspection_tool class="SerializableCtor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
|
@ -93,6 +93,12 @@ android {
|
|||
buildConfigField "String", "PERMISSION_LOCAL_BROADCAST", "\"${localBroadcastPermission}\""
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
|
@ -146,7 +152,7 @@ ext {
|
|||
coilKtVersion = "2.7.0"
|
||||
daggerVersion = "2.52"
|
||||
emojiVersion = "1.4.0"
|
||||
fidoVersion = "4.1.0-patch2"
|
||||
fidoVersion = "4.4.0"
|
||||
lifecycleVersion = '2.8.4'
|
||||
okhttpVersion = "4.12.0"
|
||||
markwonVersion = "4.6.2"
|
||||
|
@ -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'
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 10,
|
||||
"identityHash": "1b2dab0ea495c45c9c9ee6e64ba74039",
|
||||
"identityHash": "93ef64fac7a9a811c4a3c2f5a6406f87",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "User",
|
||||
|
@ -135,12 +135,539 @@
|
|||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "Conversations",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`internalId` TEXT NOT NULL, `accountId` INTEGER, `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, `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": false
|
||||
},
|
||||
{
|
||||
"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": "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, 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
|
||||
}
|
||||
],
|
||||
"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, `oldestMessageId` INTEGER NOT NULL, `newestMessageId` INTEGER NOT NULL, `hasHistory` INTEGER NOT NULL)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "internalConversationId",
|
||||
"columnName": "internalConversationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
],
|
||||
"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, '93ef64fac7a9a811c4a3c2f5a6406f87')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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.source.local.TalkDatabase
|
||||
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 chatBlocksDao: ChatBlocksDao
|
||||
private lateinit var db: TalkDatabase
|
||||
private val tag = ChatBlocksDaoTest::class.java.simpleName
|
||||
|
||||
@Before
|
||||
fun createDb() {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
db = Room.inMemoryDatabaseBuilder(
|
||||
context,
|
||||
TalkDatabase::class.java
|
||||
).build()
|
||||
chatBlocksDao = db.chatBlocksDao()
|
||||
}
|
||||
|
||||
@After
|
||||
fun closeDb() = db.close()
|
||||
|
||||
@Test
|
||||
fun testGetConnectedChatBlocks() =
|
||||
runTest {
|
||||
|
||||
val searchedChatBlock = ChatBlockEntity(
|
||||
internalConversationId = "1",
|
||||
oldestMessageId = 50,
|
||||
newestMessageId = 60,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
val chatBlockTooOld = ChatBlockEntity(
|
||||
internalConversationId = "1",
|
||||
oldestMessageId = 10,
|
||||
newestMessageId = 20,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
val chatBlockOverlap1 = ChatBlockEntity(
|
||||
internalConversationId = "1",
|
||||
oldestMessageId = 45,
|
||||
newestMessageId = 55,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
val chatBlockWithin = ChatBlockEntity(
|
||||
internalConversationId = "1",
|
||||
oldestMessageId = 52,
|
||||
newestMessageId = 58,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
val chatBlockOverall = ChatBlockEntity(
|
||||
internalConversationId = "1",
|
||||
oldestMessageId = 1,
|
||||
newestMessageId = 99,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
val chatBlockOverlap2 = ChatBlockEntity(
|
||||
internalConversationId = "1",
|
||||
oldestMessageId = 59,
|
||||
newestMessageId = 70,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
val chatBlockTooNew = ChatBlockEntity(
|
||||
internalConversationId = "1",
|
||||
oldestMessageId = 80,
|
||||
newestMessageId = 90,
|
||||
hasHistory = true
|
||||
)
|
||||
|
||||
val chatBlockWithinButOtherConversation = ChatBlockEntity(
|
||||
internalConversationId = "2",
|
||||
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(
|
||||
"1",
|
||||
searchedChatBlock.oldestMessageId,
|
||||
searchedChatBlock.newestMessageId
|
||||
)
|
||||
|
||||
assertEquals(5, results.first().size)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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.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 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<Context>()
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
private fun createChatMessageEntity(internalConversationId: String, message: String): ChatMessageEntity {
|
||||
val id = chatMessageCounter++
|
||||
|
||||
val emoji1 = "\uD83D\uDE00" // 😀
|
||||
val emoji2 = "\uD83D\uDE1C" // 😜
|
||||
val reactions = LinkedHashMap<String, Int>()
|
||||
reactions[emoji1] = 3
|
||||
reactions[emoji2] = 4
|
||||
|
||||
val reactionsSelf = ArrayList<String>()
|
||||
reactionsSelf.add(emoji1)
|
||||
|
||||
val entity = ChatMessageEntity(
|
||||
internalId = "$internalConversationId@$id",
|
||||
internalConversationId = internalConversationId,
|
||||
id = id,
|
||||
message = message,
|
||||
reactions = reactions,
|
||||
reactionsSelf = reactionsSelf
|
||||
)
|
||||
return entity
|
||||
}
|
||||
}
|
|
@ -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<ConversationItemViewHolder, GenericTextHeaderItem?>,
|
||||
IFilterable<String?> {
|
||||
private var header: GenericTextHeaderItem? = null
|
||||
private val chatMessage = model.lastMessageViaConversationList?.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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import com.google.android.material.card.MaterialCardView;
|
|||
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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
} ?: 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
|
||||
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)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
} ?: 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
|
||||
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)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
} ?: 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
|
||||
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)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import com.google.android.material.card.MaterialCardView;
|
|||
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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
} ?: 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
|
||||
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)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.messageQuote.quotedChatMessageView.visibility = View.GONE
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -33,7 +33,7 @@ public class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAda
|
|||
|
||||
@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 class TalkMessagesListAdapter<M extends IMessage> extends MessagesListAda
|
|||
} else if (holder instanceof CallStartedViewHolder) {
|
||||
((CallStartedViewHolder) holder).assignCallStartedMessageInterface(chatActivity);
|
||||
}
|
||||
|
||||
super.onBindViewHolder(holder, position);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ package com.nextcloud.talk.adapters.messages;
|
|||
|
||||
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<ChatMessage> {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -408,6 +407,7 @@ class ChatActivity :
|
|||
handleIntent(intent)
|
||||
|
||||
chatViewModel = ViewModelProvider(this, viewModelFactory)[ChatViewModel::class.java]
|
||||
|
||||
messageInputViewModel = ViewModelProvider(this, viewModelFactory)[MessageInputViewModel::class.java]
|
||||
|
||||
binding.progressBar.visibility = View.VISIBLE
|
||||
|
@ -521,12 +521,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,24 +594,29 @@ class ChatActivity :
|
|||
binding.chatToolbar.setOnClickListener { _ -> showConversationInfoScreen() }
|
||||
}
|
||||
|
||||
if (adapter == null) {
|
||||
initAdapter()
|
||||
binding.messagesListView.setAdapter(adapter)
|
||||
layoutManager = binding.messagesListView.layoutManager as LinearLayoutManager?
|
||||
}
|
||||
// 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,
|
||||
)
|
||||
|
||||
// chatViewModel.initMessagePolling(
|
||||
// withCredentials = credentials!!,
|
||||
// withUrl = urlForChatting,
|
||||
// roomToken = currentConversation!!.token!!
|
||||
// )
|
||||
}
|
||||
|
||||
is ChatViewModel.GetCapabilitiesErrorState -> {
|
||||
|
@ -705,6 +735,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 -> {
|
||||
|
@ -738,130 +773,72 @@ 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)
|
||||
|
||||
if (chatMessageList.isNotEmpty() &&
|
||||
ChatMessage.SystemMessageType.CLEARED_CHAT == chatMessageList[0].systemMessageType
|
||||
) {
|
||||
adapter?.clear()
|
||||
adapter?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
updateReadStatusOfAllMessages(newXChatLastCommonRead)
|
||||
|
||||
processCallStartedMessages(chatMessageList)
|
||||
|
||||
adapter?.notifyDataSetChanged()
|
||||
|
||||
chatViewModel.refreshChatParams(
|
||||
setupFieldsForPullChatMessages(
|
||||
true,
|
||||
newXChatLastCommonRead,
|
||||
true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
HTTP_CODE_NOT_MODIFIED -> {
|
||||
chatViewModel.refreshChatParams(
|
||||
setupFieldsForPullChatMessages(
|
||||
true,
|
||||
globalLastKnownPastMessageId,
|
||||
true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
HTTP_CODE_PRECONDITION_FAILED -> {
|
||||
chatViewModel.refreshChatParams(
|
||||
setupFieldsForPullChatMessages(
|
||||
true,
|
||||
globalLastKnownPastMessageId,
|
||||
true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
|
||||
processExpiredMessages()
|
||||
if (isFirstMessagesProcessing) {
|
||||
cancelNotificationsForCurrentConversation()
|
||||
isFirstMessagesProcessing = false
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.messagesListView.visibility = View.VISIBLE
|
||||
|
||||
collapseSystemMessages()
|
||||
}
|
||||
is ChatViewModel.ChatMessageStartState -> {
|
||||
// Handle UI on first load
|
||||
cancelNotificationsForCurrentConversation()
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.messagesListView.visibility = View.VISIBLE
|
||||
collapseSystemMessages()
|
||||
}
|
||||
|
||||
is ChatViewModel.PullChatMessageCompleteState -> {
|
||||
Log.d(TAG, "PullChatMessageCompleted")
|
||||
is ChatViewModel.ChatMessageUpdateState -> {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
is ChatViewModel.PullChatMessageErrorState -> {
|
||||
Log.d(TAG, "PullChatMessageError")
|
||||
is ChatViewModel.ChatMessageErrorState -> {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
this.lifecycleScope.launch {
|
||||
chatViewModel.getMessageFlow
|
||||
.onEach { pair ->
|
||||
val lookIntoFuture = pair.first
|
||||
var chatMessageList = pair.second
|
||||
|
||||
chatMessageList = handleSystemMessages(chatMessageList)
|
||||
|
||||
determinePreviousMessageIds(chatMessageList)
|
||||
|
||||
handleExpandableSystemMessages(chatMessageList)
|
||||
|
||||
if (chatMessageList.isNotEmpty() &&
|
||||
ChatMessage.SystemMessageType.CLEARED_CHAT == chatMessageList[0].systemMessageType
|
||||
) {
|
||||
adapter?.clear()
|
||||
adapter?.notifyDataSetChanged()
|
||||
// TODO: remove messages from DB, Should be handled beforehand (in viewModel?)
|
||||
}
|
||||
|
||||
if (lookIntoFuture) {
|
||||
processMessagesFromTheFuture(chatMessageList)
|
||||
} else {
|
||||
processMessagesNotFromTheFuture(chatMessageList)
|
||||
collapseSystemMessages()
|
||||
}
|
||||
|
||||
processCallStartedMessages(chatMessageList)
|
||||
|
||||
adapter?.notifyDataSetChanged()
|
||||
}
|
||||
.collect()
|
||||
|
||||
|
||||
chatViewModel.getUpdateMessageFlow
|
||||
.onEach { pair ->
|
||||
val lookIntoFuture = pair.first
|
||||
var chatMessageList = pair.second
|
||||
|
||||
adapter!!.update(chatMessageList[0])
|
||||
}
|
||||
.collect()
|
||||
}
|
||||
|
||||
chatViewModel.reactionDeletedViewState.observe(this) { state ->
|
||||
when (state) {
|
||||
is ChatViewModel.ReactionDeletedSuccessState -> {
|
||||
|
@ -916,6 +893,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 -> {
|
||||
|
@ -1412,15 +1394,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 +1425,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 +1808,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 +1844,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() {
|
||||
|
@ -2327,7 +2309,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 +2319,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
|
||||
|
@ -2610,9 +2592,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 +2622,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
|
||||
}
|
||||
|
@ -2721,6 +2703,7 @@ class ChatActivity :
|
|||
if (!voiceMessageToRestoreId.equals("")) {
|
||||
Log.d(RESUME_AUDIO_TAG, "begin method to resume audio playback")
|
||||
|
||||
// TODO: replace this logic by calling getItemFromAdapter(messageId)
|
||||
if (adapter != null) {
|
||||
Log.d(RESUME_AUDIO_TAG, "adapter is not null, proceeding")
|
||||
val voiceMessagePosition = adapter!!.items!!.indexOfFirst {
|
||||
|
@ -2748,7 +2731,7 @@ class ChatActivity :
|
|||
)
|
||||
}
|
||||
} else {
|
||||
Log.d(RESUME_AUDIO_TAG, "TalkMessagesListAdapater is null")
|
||||
Log.d(RESUME_AUDIO_TAG, "TalkMessagesListAdapter is null")
|
||||
}
|
||||
} else {
|
||||
Log.d(RESUME_AUDIO_TAG, "No voice message to restore")
|
||||
|
@ -2758,6 +2741,29 @@ class ChatActivity :
|
|||
voiceMessageToRestoreWasPlaying = false
|
||||
}
|
||||
|
||||
private fun getItemFromAdapter(messageId: String): ChatMessage? {
|
||||
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 currentItem
|
||||
} 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 +2777,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 {
|
||||
|
@ -2923,18 +2934,25 @@ class ChatActivity :
|
|||
|
||||
// setDeletionFlagsAndRemoveInfomessages
|
||||
if (isInfoMessageAboutDeletion(currentMessage)) {
|
||||
if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)) {
|
||||
if (!chatMessageMap.containsKey(currentMessage.value.parentMessageId.toString())) {
|
||||
// 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)
|
||||
|
||||
val id = currentMessage.value.parentMessageId.toString()
|
||||
val index = adapter?.getMessagePositionById(id) ?: 0
|
||||
|
||||
if (index > 0) {
|
||||
val message = adapter?.items?.get(index)?.item as ChatMessage
|
||||
setMessageAsDeleted(message)
|
||||
}
|
||||
} else {
|
||||
chatMessageMap[currentMessage.value.parentMessage!!.id]!!.isDeleted = true
|
||||
chatMessageMap[currentMessage.value.parentMessageId.toString()]!!.isDeleted = true
|
||||
}
|
||||
chatMessageIterator.remove()
|
||||
} else if (isReactionsMessage(currentMessage)) {
|
||||
// delete reactions system messages
|
||||
if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)) {
|
||||
updateAdapterForReaction(currentMessage.value.parentMessage)
|
||||
if (!chatMessageMap.containsKey(currentMessage.value.parentMessageId.toString())) {
|
||||
// updateAdapterForReaction(currentMessage.value.parentMessage) TODO
|
||||
}
|
||||
|
||||
chatMessageIterator.remove()
|
||||
|
@ -2942,8 +2960,8 @@ class ChatActivity :
|
|||
// delete poll system messages
|
||||
chatMessageIterator.remove()
|
||||
} else if (isEditMessage(currentMessage)) {
|
||||
if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)) {
|
||||
setMessageAsEdited(currentMessage.value.parentMessage)
|
||||
if (!chatMessageMap.containsKey(currentMessage.value.parentMessageId.toString())) {
|
||||
// setMessageAsEdited(currentMessage.value.parentMessage) TODO
|
||||
}
|
||||
|
||||
chatMessageIterator.remove()
|
||||
|
@ -2977,7 +2995,7 @@ class ChatActivity :
|
|||
}
|
||||
|
||||
private fun isInfoMessageAboutDeletion(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean {
|
||||
return currentMessage.value.parentMessage != null && currentMessage.value.systemMessageType == ChatMessage
|
||||
return currentMessage.value.parentMessageId != null && currentMessage.value.systemMessageType == ChatMessage
|
||||
.SystemMessageType.MESSAGE_DELETED
|
||||
}
|
||||
|
||||
|
@ -2988,7 +3006,7 @@ class ChatActivity :
|
|||
}
|
||||
|
||||
private fun isEditMessage(currentMessage: MutableMap.MutableEntry<String, ChatMessage>): Boolean {
|
||||
return currentMessage.value.parentMessage != null && currentMessage.value.systemMessageType == ChatMessage
|
||||
return currentMessage.value.parentMessageId != null && currentMessage.value.systemMessageType == ChatMessage
|
||||
.SystemMessageType.MESSAGE_EDITED
|
||||
}
|
||||
|
||||
|
@ -3039,7 +3057,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)
|
||||
}
|
||||
|
||||
|
@ -3285,7 +3303,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 +3368,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 +3379,43 @@ 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
|
||||
message?.let {
|
||||
val messageTemp = message as ChatMessage
|
||||
|
||||
messageTemp.isOneToOneConversation =
|
||||
currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
|
||||
messageTemp.activeUser = conversationUser
|
||||
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 +3450,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 +3554,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 +3627,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 +3721,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 +3749,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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,76 @@ 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")
|
||||
|
||||
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
|
||||
} 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
|
||||
}
|
||||
}
|
||||
|
||||
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 +773,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,
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||
* 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.models.json.chat.ChatMessageJson
|
||||
import com.nextcloud.talk.data.sync.Syncable
|
||||
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<ChatMessage>
|
||||
>
|
||||
>
|
||||
|
||||
val updateMessageFlow:
|
||||
Flow<
|
||||
Pair<
|
||||
Boolean,
|
||||
List<ChatMessage>
|
||||
>
|
||||
>
|
||||
|
||||
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<ChatMessage>
|
||||
}
|
|
@ -2,120 +2,88 @@
|
|||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2022-2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2021 Tim Krüger <t@timkrueger.me>
|
||||
* SPDX-FileCopyrightText: 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
* 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<String?, String?>? = 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<String?, HashMap<String?, String?>>? = 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> = ReadStatus.NONE,
|
||||
|
||||
@JsonField(name = ["messageType"])
|
||||
var messageType: String? = null,
|
||||
|
||||
@JsonField(name = ["reactions"])
|
||||
var reactions: LinkedHashMap<String, Int>? = null,
|
||||
|
||||
@JsonField(name = ["reactionsSelf"])
|
||||
var reactionsSelf: ArrayList<String>? = 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 {
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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<ConversationModel>
|
||||
fun getCapabilities(user: User, roomToken: String): Observable<SpreedCapability>
|
||||
fun joinRoom(user: User, roomToken: String, roomPassword: String): Observable<ConversationModel>
|
|
@ -0,0 +1,667 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
|
||||
* 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.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<ChatMessage>
|
||||
>
|
||||
>
|
||||
get() = _messageFlow
|
||||
|
||||
private val _messageFlow:
|
||||
MutableSharedFlow<
|
||||
Pair<
|
||||
Boolean,
|
||||
List<ChatMessage>
|
||||
>
|
||||
> = MutableSharedFlow()
|
||||
|
||||
override val updateMessageFlow:
|
||||
Flow<
|
||||
Pair<
|
||||
Boolean,
|
||||
List<ChatMessage>
|
||||
>
|
||||
>
|
||||
get() = _updateMessageFlow
|
||||
|
||||
private val _updateMessageFlow:
|
||||
MutableSharedFlow<
|
||||
Pair<
|
||||
Boolean,
|
||||
List<ChatMessage>
|
||||
>
|
||||
> = 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 = userProvider.currentUser.blockingGet().id!!.toString() + "@" + conversationModel.token
|
||||
internalConversationId = conversationModel.internalId
|
||||
}
|
||||
|
||||
override fun loadInitialMessages(withNetworkParams: Bundle): Job =
|
||||
scope.launch {
|
||||
Log.d(TAG, "---- loadInitialMessages ------------")
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
initMessagePolling()
|
||||
}
|
||||
|
||||
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)
|
||||
// withNetworkParams.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
|
||||
|
||||
val loadFromServer = hasToLoadPreviousMessagesFromServer(beforeMessageId)
|
||||
|
||||
if (loadFromServer) {
|
||||
if (monitor.isOnline.first()) {
|
||||
sync(withNetworkParams)
|
||||
} else {
|
||||
// TODO: handle how user is informed about gaps when being offline. Something like:
|
||||
// val offlineChatMessage = ChatMessage(
|
||||
// message = "you are offline. Some messages might be missing here."
|
||||
// )
|
||||
// val list = mutableListOf<ChatMessage>()
|
||||
// list.add(offlineChatMessage)
|
||||
//
|
||||
// if (list.isNotEmpty()) {
|
||||
// val pair = Pair(false, list)
|
||||
// _messageFlow.emit(pair)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
showLast100MessagesBefore(internalConversationId, beforeMessageId)
|
||||
}
|
||||
|
||||
override fun initMessagePolling(): Job =
|
||||
scope.launch {
|
||||
// monitor.isOnline.onEach { online ->
|
||||
|
||||
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 b/c long polling is set )
|
||||
networkParams.putSerializable(BundleKeys.KEY_FIELD_MAP, fieldMap)
|
||||
// withNetworkParams.putString(BundleKeys.KEY_ROOM_TOKEN, roomToken)
|
||||
|
||||
// this@OfflineFirstChatRepository.sync(withNetworkParams)
|
||||
// sync(withNetworkParams)
|
||||
|
||||
val resultsFromSync = sync(networkParams)
|
||||
// TODO: load from DB?! at least make sure no changes are made here that are not saved to DB then!
|
||||
|
||||
Log.d(TAG, "got result from longpolling")
|
||||
if (!resultsFromSync.isNullOrEmpty()) {
|
||||
val chatMessages = resultsFromSync.map(ChatMessageEntity::asModel)
|
||||
val pair = Pair(true, chatMessages)
|
||||
_messageFlow.emit(pair)
|
||||
}
|
||||
|
||||
// Process read status if not null
|
||||
// val lastKnown = datastore.getLastKnownId(internalConversationId, 0)
|
||||
// list = list.map { chatMessage ->
|
||||
// chatMessage.readStatus = if (chatMessage.jsonMessageId <= lastKnown) {
|
||||
// ReadStatus.READ
|
||||
// } else {
|
||||
// ReadStatus.SENT
|
||||
// }
|
||||
//
|
||||
// return@map chatMessage
|
||||
// }
|
||||
|
||||
val newestMessage2 = chatDao.getNewestMessageId(internalConversationId).toInt()
|
||||
Log.d(TAG, "newestMessage in loop: $newestMessage2")
|
||||
|
||||
// update field map vars for next cycle
|
||||
fieldMap = getFieldMap(
|
||||
lookIntoFuture = true,
|
||||
includeLastKnown = false,
|
||||
setReadMarker = true,
|
||||
lastKnown = newestMessage2
|
||||
)
|
||||
}
|
||||
// }.flowOn(Dispatchers.IO).collect()
|
||||
}
|
||||
|
||||
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<String, Int> {
|
||||
val fieldMap = HashMap<String, Int>()
|
||||
|
||||
fieldMap["includeLastKnown"] = if (includeLastKnown) 1 else 0
|
||||
|
||||
if (lastKnown != null) {
|
||||
fieldMap["lastKnownMessageId"] = lastKnown
|
||||
}
|
||||
|
||||
// newXChatLastCommonRead?.let {
|
||||
// fieldMap["lastCommonReadId"] = if (it > 0) it else lastKnown
|
||||
// }
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
private suspend fun getMessagesFrom(messageIds: List<Long>): List<ChatMessage> =
|
||||
chatDao.getMessagesFromIds(messageIds).map {
|
||||
it.map(ChatMessageEntity::asModel)
|
||||
}.first()
|
||||
|
||||
override suspend fun getMessage(messageId: Long, bundle: Bundle):
|
||||
Flow<ChatMessage> {
|
||||
|
||||
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<Int, List<ChatMessageJson>>? {
|
||||
Log.d(TAG, "An online request is made!!!!!!!!!!!!!!!!!!!!")
|
||||
// val credentials = bundle.getString(BundleKeys.KEY_CREDENTIALS)
|
||||
// val url = bundle.getString(BundleKeys.KEY_CHAT_URL)
|
||||
val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap<String, Int>
|
||||
|
||||
try {
|
||||
val result = network.pullChatMessages(credentials, urlForChatting, fieldMap)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.map {
|
||||
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)
|
||||
// }
|
||||
//
|
||||
// val xChatLastGivenHeader: String? = it.headers()["X-Chat-Last-Given"]
|
||||
// val lastKnownId = if (it.headers().size > 0 &&
|
||||
// xChatLastGivenHeader?.isNotEmpty() == true
|
||||
// ) {
|
||||
// xChatLastGivenHeader.toInt()
|
||||
// } else {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// // if (lastKnownId > 0) {
|
||||
// datastore.saveLastKnownId(internalConversationId, lastKnownId)
|
||||
// // }
|
||||
|
||||
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<ChatMessageJson>()
|
||||
)
|
||||
}
|
||||
|
||||
HTTP_CODE_PRECONDITION_FAILED -> {
|
||||
Log.d(TAG, "getMessagesFromServer HTTP_CODE_PRECONDITION_FAILED")
|
||||
|
||||
return@map Pair(
|
||||
HTTP_CODE_PRECONDITION_FAILED,
|
||||
listOf<ChatMessageJson>()
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
return@map Pair(
|
||||
HTTP_CODE_PRECONDITION_FAILED,
|
||||
listOf<ChatMessageJson>()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.blockingSingle()
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "some exception", e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun sync(bundle: Bundle): List<ChatMessageEntity>? {
|
||||
val result = getMessagesFromServer(bundle) ?: return listOf()
|
||||
var chatMessagesFromSync: List<ChatMessageEntity>? = null
|
||||
|
||||
val fieldMap = bundle.getSerializable(BundleKeys.KEY_FIELD_MAP) as HashMap<String, Int>
|
||||
val queriedMessageId = fieldMap["lastKnownMessageId"]
|
||||
val lookIntoFuture = fieldMap["lookIntoFuture"] == 1
|
||||
|
||||
val statusCode = result.first
|
||||
// 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
|
||||
|
||||
if (lookIntoFuture) {
|
||||
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,
|
||||
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<ChatMessageJson>) {
|
||||
messagesJson.forEach { messageJson ->
|
||||
when (messageJson.systemMessageType) {
|
||||
ChatMessage.SystemMessageType.REACTION -> {
|
||||
messageJson.parentMessage?.let { parentMessageJson ->
|
||||
val parentMessageEntity = parentMessageJson.asEntity(currentUser.id!!)
|
||||
chatDao.upsertChatMessage(parentMessageEntity)
|
||||
// TODO: inform UI to update this message!!
|
||||
|
||||
val pair = Pair(true, listOf(parentMessageEntity.asModel()))
|
||||
_updateMessageFlow.emit(pair)
|
||||
}
|
||||
}
|
||||
|
||||
ChatMessage.SystemMessageType.REACTION_REVOKED -> {
|
||||
// TODO
|
||||
}
|
||||
|
||||
ChatMessage.SystemMessageType.REACTION_DELETED -> {
|
||||
// TODO
|
||||
}
|
||||
|
||||
ChatMessage.SystemMessageType.MESSAGE_DELETED -> {
|
||||
// TODO
|
||||
}
|
||||
|
||||
ChatMessage.SystemMessageType.POLL_VOTED -> {
|
||||
// TODO
|
||||
}
|
||||
|
||||
ChatMessage.SystemMessageType.MESSAGE_EDITED -> {
|
||||
// TODO
|
||||
}
|
||||
|
||||
ChatMessage.SystemMessageType.CLEARED_CHAT -> {
|
||||
val pattern = "$internalConversationId%" // LIKE "<accountId>@<conversationId>@%"
|
||||
chatDao.clearAllMessagesForUser(pattern)
|
||||
}
|
||||
|
||||
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,
|
||||
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<ChatMessage> =
|
||||
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<ChatMessage> =
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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<ConversationModel> {
|
||||
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<SpreedCapability> {
|
||||
|
@ -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(
|
|
@ -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<Disposable>()
|
||||
|
||||
|
@ -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<AudioFocusRequestManager.ManagerState>
|
||||
get() = audioFocusRequestManager.getManagerState
|
||||
|
||||
|
@ -89,9 +107,26 @@ class ChatViewModel @Inject constructor(
|
|||
val getVoiceRecordingLocked: LiveData<Boolean>
|
||||
get() = _getVoiceRecordingLocked
|
||||
|
||||
private val _getFieldMapForChat: MutableLiveData<HashMap<String, Int>> = MutableLiveData()
|
||||
val getFieldMapForChat: LiveData<HashMap<String, Int>>
|
||||
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 getConversationFlow = conversationRepository.conversationFlow
|
||||
.onEach {
|
||||
_getRoomViewState.value = GetRoomSuccessState
|
||||
}.catch {
|
||||
_getRoomViewState.value = GetRoomErrorState
|
||||
}
|
||||
|
||||
sealed interface ViewState
|
||||
|
||||
object GetReminderStartState : ViewState
|
||||
|
@ -111,7 +146,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<ViewState> = MutableLiveData(GetRoomStartState)
|
||||
val getRoomViewState: LiveData<ViewState>
|
||||
|
@ -136,28 +171,24 @@ class ChatViewModel @Inject constructor(
|
|||
|
||||
object LeaveRoomStartState : ViewState
|
||||
class LeaveRoomSuccessState(val funToCallWhenLeaveSuccessful: (() -> Unit)?) : ViewState
|
||||
|
||||
private val _leaveRoomViewState: MutableLiveData<ViewState> = MutableLiveData(LeaveRoomStartState)
|
||||
val leaveRoomViewState: LiveData<ViewState>
|
||||
get() = _leaveRoomViewState
|
||||
|
||||
object SendChatMessageStartState : ViewState
|
||||
class SendChatMessageSuccessState(val message: CharSequence) : ViewState
|
||||
class SendChatMessageErrorState(val e: Throwable, val message: CharSequence) : ViewState
|
||||
private val _sendChatMessageViewState: MutableLiveData<ViewState> = MutableLiveData(SendChatMessageStartState)
|
||||
val sendChatMessageViewState: LiveData<ViewState>
|
||||
get() = _sendChatMessageViewState
|
||||
object ChatMessageInitialState : ViewState
|
||||
object ChatMessageStartState : ViewState
|
||||
object ChatMessageUpdateState : ViewState
|
||||
object ChatMessageErrorState : ViewState
|
||||
|
||||
object PullChatMessageStartState : ViewState
|
||||
class PullChatMessageSuccessState(val response: Response<*>, val lookIntoFuture: Boolean) : ViewState
|
||||
object PullChatMessageErrorState : ViewState
|
||||
object PullChatMessageCompleteState : ViewState
|
||||
private val _pullChatMessageViewState: MutableLiveData<ViewState> = MutableLiveData(PullChatMessageStartState)
|
||||
val pullChatMessageViewState: LiveData<ViewState>
|
||||
get() = _pullChatMessageViewState
|
||||
private val _chatMessageViewState: MutableLiveData<ViewState> = MutableLiveData(ChatMessageInitialState)
|
||||
val chatMessageViewState: LiveData<ViewState>
|
||||
get() = _chatMessageViewState
|
||||
|
||||
object DeleteChatMessageStartState : ViewState
|
||||
class DeleteChatMessageSuccessState(val msg: ChatOverallSingleMessage) : ViewState
|
||||
object DeleteChatMessageErrorState : ViewState
|
||||
|
||||
private val _deleteChatMessageViewState: MutableLiveData<ViewState> = MutableLiveData(DeleteChatMessageStartState)
|
||||
val deleteChatMessageViewState: LiveData<ViewState>
|
||||
get() = _deleteChatMessageViewState
|
||||
|
@ -172,29 +203,38 @@ class ChatViewModel @Inject constructor(
|
|||
|
||||
object ReactionAddedStartState : ViewState
|
||||
class ReactionAddedSuccessState(val reactionAddedModel: ReactionAddedModel) : ViewState
|
||||
|
||||
private val _reactionAddedViewState: MutableLiveData<ViewState> = MutableLiveData(ReactionAddedStartState)
|
||||
val reactionAddedViewState: LiveData<ViewState>
|
||||
get() = _reactionAddedViewState
|
||||
|
||||
object ReactionDeletedStartState : ViewState
|
||||
class ReactionDeletedSuccessState(val reactionDeletedModel: ReactionDeletedModel) : ViewState
|
||||
|
||||
private val _reactionDeletedViewState: MutableLiveData<ViewState> = MutableLiveData(ReactionDeletedStartState)
|
||||
val reactionDeletedViewState: LiveData<ViewState>
|
||||
get() = _reactionDeletedViewState
|
||||
|
||||
fun refreshChatParams(pullChatMessagesFieldMap: HashMap<String, Int>, 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 +248,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<SpreedCapability> {
|
||||
|
@ -238,7 +278,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 +286,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<GenericOverall> {
|
||||
|
@ -284,7 +324,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<GenericOverall> {
|
||||
|
@ -309,7 +349,7 @@ class ChatViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
fun createRoom(credentials: String, url: String, queryMap: Map<String, String>) {
|
||||
chatRepository.createRoom(credentials, url, queryMap)
|
||||
chatNetworkDataSource.createRoom(credentials, url, queryMap)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Observer<RoomOverall> {
|
||||
|
@ -332,72 +372,42 @@ class ChatViewModel @Inject constructor(
|
|||
})
|
||||
}
|
||||
|
||||
fun sendChatMessage(
|
||||
credentials: String,
|
||||
url: String,
|
||||
message: CharSequence,
|
||||
displayName: String,
|
||||
replyTo: Int,
|
||||
sendWithoutNotification: Boolean
|
||||
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 loadMoreMessages(
|
||||
beforeMessageId: Long,
|
||||
roomToken: String,
|
||||
withMessageLimit: Int,
|
||||
withCredentials: String,
|
||||
withUrl: String
|
||||
) {
|
||||
chatRepository.sendChatMessage(
|
||||
credentials,
|
||||
url,
|
||||
message,
|
||||
displayName,
|
||||
replyTo,
|
||||
sendWithoutNotification
|
||||
).subscribeOn(Schedulers.io())
|
||||
?.observeOn(AndroidSchedulers.mainThread())
|
||||
?.subscribe(object : Observer<GenericOverall> {
|
||||
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)
|
||||
}
|
||||
})
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_CHAT_URL, withUrl)
|
||||
bundle.putString(BundleKeys.KEY_CREDENTIALS, withCredentials)
|
||||
chatRepository.loadMoreMessages(
|
||||
beforeMessageId,
|
||||
roomToken,
|
||||
withMessageLimit,
|
||||
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<Response<*>> {
|
||||
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 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<ChatOverallSingleMessage> {
|
||||
|
@ -426,7 +436,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<GenericOverall> {
|
||||
|
@ -449,7 +459,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<GenericOverall> {
|
||||
|
@ -472,13 +482,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<GenericOverall> {
|
||||
|
@ -575,6 +585,7 @@ class ChatViewModel @Inject constructor(
|
|||
uploadFile(uri.toString(), room, displayName, metaData)
|
||||
}
|
||||
}
|
||||
|
||||
fun stopAndDiscardAudioRecording() {
|
||||
stopAudioRecording()
|
||||
Log.d(TAG, "File discarded")
|
||||
|
@ -619,24 +630,38 @@ class ChatViewModel @Inject constructor(
|
|||
_getCapabilitiesViewState.value = GetCapabilitiesStartState
|
||||
}
|
||||
|
||||
inner class GetRoomObserver : Observer<ConversationModel> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
// unused atm
|
||||
suspend fun getMessageById(url: String, conversationModel: ConversationModel, messageId: Long): Flow<ChatMessage> =
|
||||
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!!)
|
||||
|
||||
val message = chatRepository.getMessage(messageId, bundle)
|
||||
emit(message.first())
|
||||
}
|
||||
|
||||
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 GetRoomObserver : Observer<ConversationModel> {
|
||||
// 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<ConversationModel> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
|
@ -704,7 +729,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!!)
|
||||
|
|
|
@ -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<Disposable>()
|
||||
|
||||
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<QueuedMessage> = 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<ChatOverallSingleMessage> {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<String> = ArrayList()
|
||||
private var groupsToInvite: ArrayList<String> = ArrayList()
|
||||
private var emailsToInvite: ArrayList<String> = ArrayList()
|
||||
|
|
|
@ -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<GenericOverall>
|
||||
|
||||
fun createConversation(roomName: String, conversationType: Conversation.ConversationType?): Observable<RoomOverall>
|
||||
fun createConversation(
|
||||
roomName: String,
|
||||
conversationType: ConversationEnums.ConversationType?
|
||||
): Observable<RoomOverall>
|
||||
}
|
||||
|
|
|
@ -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<RoomOverall> {
|
||||
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())
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<SpreedCapability> {
|
||||
|
@ -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<List<TalkBan>> {
|
||||
|
@ -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<TalkBan> {
|
||||
|
@ -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<GenericOverall> {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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<ConversationModel> {
|
||||
return ncApi.deleteConversationAvatar(
|
||||
credentials,
|
||||
ApiUtils.getUrlForConversationAvatar(1, user.baseUrl!!, roomToken)
|
||||
).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
|
||||
).map { ConversationModel.mapToConversationModel(it.ocs?.data!!, user) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -91,8 +92,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
|
||||
|
@ -107,6 +108,7 @@ 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
|
||||
|
@ -190,7 +195,7 @@ class ConversationsListActivity :
|
|||
private var isRefreshing = false
|
||||
private var showShareToScreen = false
|
||||
private var filesToShare: ArrayList<String>? = 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 +264,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()
|
||||
|
@ -334,6 +339,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 +424,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 +433,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 +623,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 +636,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 +652,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,67 +731,68 @@ class ConversationsListActivity :
|
|||
}
|
||||
|
||||
fun fetchRooms() {
|
||||
val includeStatus = isUserStatusAvailable(userManager.currentUser.blockingGet())
|
||||
val includeStatus = isUserStatusAvailable(currentUser!!)
|
||||
conversationsListViewModel.getRooms()
|
||||
|
||||
// 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
|
||||
}
|
||||
// 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()
|
||||
|
@ -760,31 +811,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
|
||||
) {
|
||||
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
|
||||
}
|
||||
|
@ -909,35 +960,35 @@ class ConversationsListActivity :
|
|||
)
|
||||
) {
|
||||
val openConversationItems: MutableList<AbstractFlexibleItem<*>> = 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 +1030,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 +1066,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 +1092,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 +1117,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 +1208,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 +1265,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 +1295,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() {
|
||||
|
@ -1519,7 +1570,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 +1802,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 +1861,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()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.conversationlist.data
|
||||
|
||||
interface ConversationsListRepository
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.conversationlist.data
|
||||
|
||||
import com.nextcloud.talk.data.sync.Syncable
|
||||
import com.nextcloud.talk.models.domain.ConversationModel
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface OfflineConversationsRepository : Syncable {
|
||||
|
||||
/**
|
||||
* Stream of a list of rooms, for use in the conversation list.
|
||||
*/
|
||||
val roomListFlow: Flow<List<ConversationModel>>
|
||||
|
||||
/**
|
||||
* Stream of a single conversation, for use in each conversations settings.
|
||||
*/
|
||||
val conversationFlow: Flow<ConversationModel>
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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<List<Conversation>>
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.conversationlist.data.network
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.core.os.bundleOf
|
||||
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.sync.Synchronizer
|
||||
import com.nextcloud.talk.data.sync.changeListSync
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.models.domain.ConversationModel
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
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 currentUserProviderNew: CurrentUserProviderNew
|
||||
) : OfflineConversationsRepository, Synchronizer {
|
||||
|
||||
override val roomListFlow: Flow<List<ConversationModel>>
|
||||
get() = _roomListFlow
|
||||
private val _roomListFlow: MutableSharedFlow<List<ConversationModel>> = MutableSharedFlow()
|
||||
|
||||
override val conversationFlow: Flow<ConversationModel>
|
||||
get() = _conversationFlow
|
||||
private val _conversationFlow: MutableSharedFlow<ConversationModel> = MutableSharedFlow()
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
private var user: User = currentUserProviderNew.currentUser.blockingGet()
|
||||
|
||||
override fun getRooms(): Job =
|
||||
scope.launch {
|
||||
repeat(2) {
|
||||
val list = getListOfConversations(user.id!!)
|
||||
if (list.isNotEmpty()) {
|
||||
_roomListFlow.emit(list)
|
||||
}
|
||||
this@OfflineFirstConversationsRepository.sync(bundleOf())
|
||||
}
|
||||
}
|
||||
|
||||
override fun getConversationSettings(roomToken: String): Job =
|
||||
scope.launch {
|
||||
val id = user.id!!
|
||||
val model = getConversation(id, roomToken)
|
||||
model?.let { _conversationFlow.emit(model) }
|
||||
}
|
||||
|
||||
override suspend fun syncWith(bundle: Bundle, synchronizer: Synchronizer): Boolean =
|
||||
synchronizer.changeListSync(
|
||||
modelFetcher = {
|
||||
return@changeListSync getConversationsFromServer()
|
||||
},
|
||||
// not needed
|
||||
versionUpdater = {},
|
||||
modelDeleter = {},
|
||||
modelUpdater = { models ->
|
||||
val list = models.filterIsInstance<Conversation>().map {
|
||||
it.asEntity(user.id!!)
|
||||
}
|
||||
dao.upsertConversations(list)
|
||||
}
|
||||
)
|
||||
|
||||
private fun getConversationsFromServer(): List<Conversation> {
|
||||
val list = network.getRooms(user, user.baseUrl!!, false)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.map { list ->
|
||||
return@map list.map {
|
||||
it.apply {
|
||||
id = roomId!!.toLong()
|
||||
}
|
||||
}
|
||||
}
|
||||
.blockingSingle()
|
||||
|
||||
return list ?: listOf()
|
||||
}
|
||||
|
||||
private suspend fun getListOfConversations(accountId: Long): List<ConversationModel> =
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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<List<Conversation>> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<ViewState> = MutableLiveData(GetRoomsStartState)
|
||||
val getRoomsViewState: LiveData<ViewState>
|
||||
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<InvitationsModel> {
|
||||
override fun onSubscribe(d: Disposable) {
|
||||
// unused atm
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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()
|
||||
}
|
|
@ -9,6 +9,8 @@ package com.nextcloud.talk.dagger.modules;
|
|||
|
||||
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 class DatabaseModule {
|
|||
@NonNull final AppPreferences appPreferences) {
|
||||
return TalkDatabase.getInstance(context, appPreferences);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public NetworkMonitor provideNetworkMonitor(@NonNull final Context poContext) {
|
||||
return new NetworkMonitorImpl(poContext);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2022-2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
|
||||
* 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,34 @@ 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,
|
||||
currentUserProviderNew: CurrentUserProviderNew
|
||||
): OfflineConversationsRepository {
|
||||
return OfflineFirstConversationsRepository(dao, dataSource, currentUserProviderNew)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideContactsRepository(ncApiCoroutines: NcApiCoroutines, userManager: UserManager): ContactsRepository {
|
||||
return ContactsRepositoryImpl(ncApiCoroutines, userManager)
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.data.changeListVersion
|
||||
|
||||
/**
|
||||
* Models any changes from the network, agnostic to what data is being modeled.
|
||||
* Implemented by Models that support offline synchronization.
|
||||
*/
|
||||
interface SyncableModel {
|
||||
|
||||
/**
|
||||
* Model identifier.
|
||||
*/
|
||||
var id: Long
|
||||
|
||||
/**
|
||||
* Model deletion checker.
|
||||
*/
|
||||
var markedForDeletion: Boolean
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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<ChatBlockEntity>)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM ChatBlocks
|
||||
WHERE internalConversationId in (:internalConversationId)
|
||||
ORDER BY newestMessageId ASC
|
||||
"""
|
||||
)
|
||||
fun getChatBlocks(internalConversationId: String): Flow<List<ChatBlockEntity>>
|
||||
|
||||
// @Query(
|
||||
// """
|
||||
// SELECT *
|
||||
// FROM ChatBlocks
|
||||
// WHERE internalConversationId in (:internalConversationId)
|
||||
// AND newestMessageId >= :messageId
|
||||
// ORDER BY newestMessageId ASC
|
||||
// """
|
||||
// )
|
||||
// fun getChatBlocksThatReachMessageId(
|
||||
// internalConversationId: String,
|
||||
// messageId: Long
|
||||
// ):
|
||||
// Flow<List<ChatBlockEntity>>
|
||||
|
||||
@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<List<ChatBlockEntity?>>
|
||||
|
||||
|
||||
@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<List<ChatBlockEntity>>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertChatBlock(chatBlock: ChatBlockEntity)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
DELETE FROM ChatBlocks
|
||||
WHERE internalConversationId LIKE :pattern
|
||||
"""
|
||||
)
|
||||
fun clearChatBlocksForUser(pattern: String)
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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<List<ChatMessageEntity>>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun upsertChatMessages(chatMessages: List<ChatMessageEntity>)
|
||||
|
||||
@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<ChatMessageEntity>
|
||||
|
||||
@Query(
|
||||
value = """
|
||||
DELETE FROM ChatMessages
|
||||
WHERE id in (:messageIds)
|
||||
"""
|
||||
)
|
||||
fun deleteChatMessages(messageIds: List<Int>)
|
||||
|
||||
@Update
|
||||
fun updateChatMessage(message: ChatMessageEntity)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM ChatMessages
|
||||
WHERE id in (:messageIds)
|
||||
ORDER BY timestamp ASC, id ASC
|
||||
"""
|
||||
)
|
||||
fun getMessagesFromIds(messageIds: List<Long>): Flow<List<ChatMessageEntity>>
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT *
|
||||
FROM ChatMessages
|
||||
WHERE internalConversationId = :internalConversationId AND id >= :messageId
|
||||
ORDER BY timestamp ASC, id ASC
|
||||
"""
|
||||
)
|
||||
fun getMessagesForConversationSince(internalConversationId: String, messageId: Long): Flow<List<ChatMessageEntity>>
|
||||
|
||||
@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<List<ChatMessageEntity>>
|
||||
|
||||
@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<List<ChatMessageEntity>>
|
||||
|
||||
@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)
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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<List<ConversationEntity>>
|
||||
|
||||
@Query("SELECT * FROM Conversations where accountId = :accountId AND token = :token")
|
||||
fun getConversationForUser(accountId: Long, token: String): Flow<ConversationEntity>
|
||||
|
||||
@Upsert
|
||||
fun upsertConversations(conversationEntities: List<ConversationEntity>)
|
||||
|
||||
/**
|
||||
* Deletes rows in the db matching the specified [conversationIds]
|
||||
*/
|
||||
@Query(
|
||||
value = """
|
||||
DELETE FROM conversations
|
||||
WHERE internalId in (:conversationIds)
|
||||
"""
|
||||
)
|
||||
fun deleteConversation(conversationIds: List<Long>)
|
||||
|
||||
@Update
|
||||
fun updateConversation(conversationEntity: ConversationEntity)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
DELETE FROM conversations
|
||||
WHERE internalId LIKE :pattern
|
||||
"""
|
||||
)
|
||||
fun clearAllConversationsForUser(pattern: String)
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
|
||||
* 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
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
)
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||
* 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,
|
||||
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,
|
||||
// lastMessageId = lastMessage?.id?.toLong(),
|
||||
objectType = objectType,
|
||||
notificationLevel = notificationLevel,
|
||||
conversationReadOnlyState = conversationReadOnlyState,
|
||||
lobbyState = lobbyState,
|
||||
lobbyTimer = lobbyTimer,
|
||||
lastReadMessage = lastReadMessage,
|
||||
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,
|
||||
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,
|
||||
lastMessageViaConversationList = lastMessageJson?.let
|
||||
{ LoganSquare.parse(lastMessageJson, ChatMessageJson::class.java) },
|
||||
objectType = objectType,
|
||||
notificationLevel = notificationLevel,
|
||||
conversationReadOnlyState = conversationReadOnlyState,
|
||||
lobbyState = lobbyState,
|
||||
lobbyTimer = lobbyTimer,
|
||||
lastReadMessage = lastReadMessage,
|
||||
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,
|
||||
lastMessageJson = lastMessage?.let { LoganSquare.serialize(lastMessage) },
|
||||
objectType = objectType,
|
||||
notificationLevel = notificationLevel,
|
||||
conversationReadOnlyState = conversationReadOnlyState,
|
||||
lobbyState = lobbyState,
|
||||
lobbyTimer = lobbyTimer,
|
||||
lastReadMessage = lastReadMessage,
|
||||
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
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
tableName = "ChatBlocks"
|
||||
// indices = [
|
||||
// androidx.room.Index(value = ["accountId"])
|
||||
// ]
|
||||
)
|
||||
data class ChatBlockEntity(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = "id") var id: Int = 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
|
||||
)
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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(
|
||||
@PrimaryKey
|
||||
// accountId@roomtoken@messageId
|
||||
@ColumnInfo(name = "internalId") var internalId: String,
|
||||
@ColumnInfo(name = "accountId") var accountId: Long? = null,
|
||||
@ColumnInfo(name = "token") var token: String? = null,
|
||||
@ColumnInfo(name = "id") var id: Long = 0,
|
||||
// accountId@roomtoken
|
||||
@ColumnInfo(name = "internalConversationId") var internalConversationId: String? = null,
|
||||
|
||||
@ColumnInfo(name = "actorType") var actorType: String? = null,
|
||||
@ColumnInfo(name = "actorId") var actorId: String? = null,
|
||||
@ColumnInfo(name = "actorDisplayName") var actorDisplayName: String? = null,
|
||||
@ColumnInfo(name = "timestamp") var timestamp: Long = 0,
|
||||
@ColumnInfo(name = "systemMessage") var systemMessageType: ChatMessage.SystemMessageType? = null,
|
||||
@ColumnInfo(name = "messageType") var messageType: String? = null,
|
||||
@ColumnInfo(name = "isReplyable") var replyable: Boolean = false,
|
||||
// TODO: add "referenceId"
|
||||
@ColumnInfo(name = "message") var message: String? = null,
|
||||
@ColumnInfo(name = "messageParameters") var messageParameters: HashMap<String?, HashMap<String?, String?>>? = null,
|
||||
@ColumnInfo(name = "expirationTimestamp") var expirationTimestamp: Int = 0,
|
||||
@ColumnInfo(name = "parent") var parentMessageId: Long? = null,
|
||||
@ColumnInfo(name = "reactions") var reactions: LinkedHashMap<String, Int>? = null,
|
||||
@ColumnInfo(name = "reactionsSelf") var reactionsSelf: ArrayList<String>? = null,
|
||||
@ColumnInfo(name = "markdown") var renderMarkdown: Boolean? = null,
|
||||
@ColumnInfo(name = "lastEditActorType") var lastEditActorType: String? = null,
|
||||
@ColumnInfo(name = "lastEditActorId") var lastEditActorId: String? = null,
|
||||
@ColumnInfo(name = "lastEditActorDisplayName") var lastEditActorDisplayName: String? = null,
|
||||
@ColumnInfo(name = "lastEditTimestamp") var lastEditTimestamp: Long? = 0
|
||||
// TODO: add "silent"
|
||||
)
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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.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 = [
|
||||
androidx.room.Index(value = ["accountId"])
|
||||
]
|
||||
)
|
||||
data class ConversationEntity(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "internalId")
|
||||
var internalId: String,
|
||||
|
||||
// Defines to which talk app account this conversation belongs to
|
||||
@ColumnInfo(name = "accountId") var accountId: Long? = null,
|
||||
|
||||
// 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 = "name") var name: String? = null,
|
||||
@ColumnInfo(name = "displayName") var displayName: String? = null,
|
||||
@ColumnInfo(name = "description") var description: String? = null,
|
||||
@ColumnInfo(name = "type") var type: ConversationEnums.ConversationType? = null,
|
||||
@ColumnInfo(name = "lastPing") var lastPing: Long = 0,
|
||||
// TODO FIX type
|
||||
@ColumnInfo(name = "participantType") var participantType: Participant.ParticipantType? = null,
|
||||
@ColumnInfo(name = "hasPassword") var hasPassword: Boolean = false,
|
||||
@ColumnInfo(name = "sessionId") var sessionId: String? = null,
|
||||
@ColumnInfo(name = "actorId") var actorId: String? = null,
|
||||
@ColumnInfo(name = "actorType") var actorType: String? = null,
|
||||
@ColumnInfo(name = "isFavorite") var favorite: Boolean = false,
|
||||
@ColumnInfo(name = "lastActivity") var lastActivity: Long = 0,
|
||||
@ColumnInfo(name = "unreadMessages") var unreadMessages: Int = 0,
|
||||
@ColumnInfo(name = "unreadMention") var unreadMention: Boolean = false,
|
||||
@ColumnInfo(name = "lastMessageJson") var lastMessageJson: String? = null,
|
||||
@ColumnInfo(name = "objectType") var objectType: ConversationEnums.ObjectType? = null,
|
||||
@ColumnInfo(name = "notificationLevel") var notificationLevel: ConversationEnums.NotificationLevel? = null,
|
||||
@ColumnInfo(name = "readOnly") var conversationReadOnlyState: ConversationEnums.ConversationReadOnlyState? = null,
|
||||
@ColumnInfo(name = "lobbyState") var lobbyState: ConversationEnums.LobbyState? = null,
|
||||
@ColumnInfo(name = "lobbyTimer") var lobbyTimer: Long? = null,
|
||||
@ColumnInfo(name = "lastReadMessage") var lastReadMessage: Int = 0,
|
||||
@ColumnInfo(name = "hasCall") var hasCall: Boolean = false,
|
||||
@ColumnInfo(name = "callFlag") var callFlag: Int = 0,
|
||||
@ColumnInfo(name = "canStartCall") var canStartCall: Boolean = false,
|
||||
@ColumnInfo(name = "canLeaveConversation") var canLeaveConversation: Boolean? = null,
|
||||
@ColumnInfo(name = "canDeleteConversation") var canDeleteConversation: Boolean? = null,
|
||||
@ColumnInfo(name = "unreadMentionDirect") var unreadMentionDirect: Boolean? = null,
|
||||
@ColumnInfo(name = "notificationCalls") var notificationCalls: Int? = null,
|
||||
@ColumnInfo(name = "permissions") var permissions: Int = 0,
|
||||
@ColumnInfo(name = "messageExpiration") var messageExpiration: Int = 0,
|
||||
@ColumnInfo(name = "status") var status: String? = null,
|
||||
@ColumnInfo(name = "statusIcon") var statusIcon: String? = null,
|
||||
@ColumnInfo(name = "statusMessage") var statusMessage: String? = null,
|
||||
@ColumnInfo(name = "statusClearAt") var statusClearAt: Long? = 0,
|
||||
@ColumnInfo(name = "callRecording") var callRecording: Int = 0,
|
||||
@ColumnInfo(name = "avatarVersion") var avatarVersion: String? = null,
|
||||
@ColumnInfo(name = "isCustomAvatar") var hasCustomAvatar: Boolean? = null,
|
||||
@ColumnInfo(name = "callStartTime") var callStartTime: Long? = null,
|
||||
@ColumnInfo(name = "recordingConsent") var recordingConsentRequired: Int = 0,
|
||||
@ColumnInfo(name = "remoteServer") var remoteServer: String? = null,
|
||||
@ColumnInfo(name = "remoteToken") var remoteToken: String? = null
|
||||
)
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
|
||||
* 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<Boolean>
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
|
||||
* 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<Boolean> = callbackFlow {
|
||||
trace("NetworkMonitorImpl.callbackFlow") {
|
||||
val connectivityManager = context.getSystemService<ConnectivityManager>()
|
||||
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<Network>()
|
||||
|
||||
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
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2023-2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-FileCopyrightText: 2017-2020 Mario Danic <mario@lovelyhq.com>
|
||||
* 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,10 +40,15 @@ 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],
|
||||
entities = [
|
||||
UserEntity::class,
|
||||
ArbitraryStorageEntity::class,
|
||||
ConversationEntity::class,
|
||||
ChatMessageEntity::class,
|
||||
ChatBlockEntity::class
|
||||
],
|
||||
version = 10,
|
||||
autoMigrations = [
|
||||
AutoMigration(from = 9, to = 10)
|
||||
|
@ -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 {
|
||||
|
@ -89,7 +108,7 @@ abstract class TalkDatabase : RoomDatabase() {
|
|||
return Room
|
||||
.databaseBuilder(context.applicationContext, TalkDatabase::class.java, dbName)
|
||||
// comment out openHelperFactory to view the database entries in Android Studio for debugging
|
||||
.openHelperFactory(factory)
|
||||
// .openHelperFactory(factory) // TODO: uncomment when offline support is production ready!!!!!!!
|
||||
.addMigrations(Migrations.MIGRATION_6_8, Migrations.MIGRATION_7_8, Migrations.MIGRATION_8_9)
|
||||
.allowMainThreadQueries()
|
||||
.addCallback(
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Julius Linus <juliuslinus1@gmail.com>
|
||||
* 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>?): 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<String>? {
|
||||
if (value.isNullOrEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return LoganSquare.parseList(value, List::class.java) as ArrayList<String>?
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ import com.bluelinelabs.logansquare.LoganSquare
|
|||
|
||||
class HashMapHashMapConverter {
|
||||
@TypeConverter
|
||||
fun fromDoubleHashMapToString(map: HashMap<String, HashMap<String, String>>?): String? {
|
||||
fun fromDoubleHashMapToString(map: HashMap<String?, HashMap<String?, String?>>?): String? {
|
||||
return if (map == null) {
|
||||
LoganSquare.serialize(hashMapOf<String, HashMap<String, String>>())
|
||||
} else {
|
||||
|
@ -21,11 +21,11 @@ class HashMapHashMapConverter {
|
|||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromStringToDoubleHashMap(value: String?): HashMap<String, HashMap<String, String>>? {
|
||||
fun fromStringToDoubleHashMap(value: String?): HashMap<String?, HashMap<String?, String?>>? {
|
||||
if (value.isNullOrEmpty()) {
|
||||
return hashMapOf()
|
||||
}
|
||||
|
||||
return LoganSquare.parseMap(value, HashMap::class.java) as HashMap<String, HashMap<String, String>>?
|
||||
return LoganSquare.parseMap(value, HashMap::class.java) as HashMap<String?, HashMap<String?, String?>>?
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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<String, Int> {
|
||||
if (value.isNullOrEmpty() || value == "{}") {
|
||||
return linkedMapOf()
|
||||
}
|
||||
// "{"👍":1,"👎":1,"😃":1,"😯":1}" // pretend this is value
|
||||
return try {
|
||||
val map = linkedMapOf<String, Int>()
|
||||
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, Int>?): 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()
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
|
||||
* 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<LinkedHashMap<String, Int>> {
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun parse(jsonParser: JsonParser?): LinkedHashMap<String, Int> {
|
||||
val map: LinkedHashMap<String, Int> = 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<String, Int>?,
|
||||
fieldName: String?,
|
||||
writeFieldNameForObject: Boolean,
|
||||
jsonGenerator: JsonGenerator?
|
||||
) {
|
||||
jsonGenerator?.apply {
|
||||
if (fieldName != null) {
|
||||
writeFieldName(fieldName)
|
||||
}
|
||||
writeStartObject()
|
||||
`object`?.forEach { (key, value) ->
|
||||
writeFieldName(key)
|
||||
writeNumber(value)
|
||||
}
|
||||
writeEndObject()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.data.sync
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import com.nextcloud.talk.data.changeListVersion.SyncableModel
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
|
||||
/**
|
||||
* Interface marker for a class that manages synchronization between local data and a remote
|
||||
* source for a [Syncable].
|
||||
*/
|
||||
interface Synchronizer {
|
||||
|
||||
// TODO include any other helper functions here that the Synchronizer needs
|
||||
|
||||
/**
|
||||
* Syntactic sugar to call [Syncable.syncWith] while omitting the synchronizer argument
|
||||
*/
|
||||
suspend fun Syncable.sync(bundle: Bundle) = this@sync.syncWith(bundle, this@Synchronizer)
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface marker for a class that is synchronized with a remote source. Syncing must not be
|
||||
* performed concurrently and it is the [Synchronizer]'s responsibility to ensure this.
|
||||
*/
|
||||
interface Syncable {
|
||||
/**
|
||||
* Synchronizes the local database backing the repository with the network.
|
||||
* Takes in a [bundle] to retrieve other metadata needed
|
||||
*
|
||||
* Returns if the sync was successful or not.
|
||||
*/
|
||||
suspend fun syncWith(bundle: Bundle, synchronizer: Synchronizer): Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts [block], returning a successful [Result] if it succeeds, otherwise a [Result.Failure]
|
||||
* taking care not to break structured concurrency
|
||||
*/
|
||||
private suspend fun <T> suspendRunCatching(block: suspend () -> T): Result<T> =
|
||||
try {
|
||||
Result.success(block())
|
||||
} catch (cancellationException: CancellationException) {
|
||||
throw cancellationException
|
||||
} catch (exception: Exception) {
|
||||
Log.e(
|
||||
"suspendRunCatching",
|
||||
"Failed to evaluate a suspendRunCatchingBlock. Returning failure Result",
|
||||
exception
|
||||
)
|
||||
Result.failure(exception)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for syncing a repository with the network.
|
||||
* [modelFetcher] Fetches the change list for the model
|
||||
* [versionUpdater] Updates the version after a successful sync
|
||||
* [modelDeleter] Deletes models by consuming the ids of the models that have been deleted.
|
||||
* [modelUpdater] Updates models by consuming the ids of the models that have changed.
|
||||
*
|
||||
* Note that the blocks defined above are never run concurrently, and the [Synchronizer]
|
||||
* implementation must guarantee this.
|
||||
*/
|
||||
suspend fun Synchronizer.changeListSync(
|
||||
modelFetcher: suspend () -> List<SyncableModel>,
|
||||
versionUpdater: (Long) -> Unit,
|
||||
modelDeleter: suspend (List<Long>) -> Unit,
|
||||
modelUpdater: suspend (List<SyncableModel>) -> Unit
|
||||
) = suspendRunCatching {
|
||||
// Fetch the change list since last sync (akin to a git fetch)
|
||||
val changeList = modelFetcher()
|
||||
if (changeList.isEmpty()) return@suspendRunCatching true
|
||||
|
||||
// Splits the models marked for deletion from the ones that are updated or new
|
||||
val (deleted, updated) = changeList.partition(SyncableModel::markedForDeletion)
|
||||
|
||||
// Delete models that have been deleted server-side
|
||||
modelDeleter(deleted.map(SyncableModel::id))
|
||||
|
||||
// Using the fetch list, pull down and upsert the changes (akin to a git pull)
|
||||
modelUpdater(updated)
|
||||
|
||||
// Update the last synced version (akin to updating local git HEAD)
|
||||
val latestVersion = changeList.last().id
|
||||
versionUpdater(latestVersion)
|
||||
}.isSuccess
|
|
@ -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)
|
||||
|
|
|
@ -16,6 +16,9 @@ import com.nextcloud.talk.R;
|
|||
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 @@ import retrofit2.Retrofit;
|
|||
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;
|
||||
|
||||
|
@ -177,6 +182,7 @@ public class AccountRemovalWorker extends Worker {
|
|||
|
||||
try {
|
||||
arbitraryStorageManager.deleteAllEntriesForAccountIdentifier(user.getId());
|
||||
deleteAllUserInfo(user);
|
||||
deleteUser(user);
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "error while trying to delete All Entries For Account Identifier", e);
|
||||
|
@ -184,6 +190,14 @@ public class AccountRemovalWorker extends Worker {
|
|||
}
|
||||
}
|
||||
|
||||
private void deleteAllUserInfo(User user) {
|
||||
String accountId = Objects.requireNonNull(user.getId()).toString();
|
||||
String pattern = accountId + "@%"; // ... LIKE "<accountId>@%"
|
||||
chatMessagesDao.clearAllMessagesForUser(pattern);
|
||||
conversationsDao.clearAllConversationsForUser(pattern);
|
||||
chatBlocksDao.clearChatBlocksForUser(pattern);
|
||||
}
|
||||
|
||||
private void deleteUser(User user) {
|
||||
if (user.getId() != null) {
|
||||
String username = user.getUsername();
|
||||
|
|
|
@ -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<ConversationModel> {
|
||||
|
|
|
@ -7,17 +7,23 @@
|
|||
*/
|
||||
package com.nextcloud.talk.models.domain
|
||||
|
||||
import com.nextcloud.talk.data.changeListVersion.SyncableModel
|
||||
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 internalId: String,
|
||||
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 type: ConversationEnums.ConversationType? = null,
|
||||
var lastPing: Long = 0,
|
||||
var participantType: ParticipantType? = null,
|
||||
var participantType: Participant.ParticipantType? = null,
|
||||
var hasPassword: Boolean = false,
|
||||
var sessionId: String? = null,
|
||||
var actorId: String? = null,
|
||||
|
@ -27,11 +33,12 @@ class ConversationModel(
|
|||
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 lastMessageViaConversationList: LastMessageJson? = null,
|
||||
var lastMessageViaConversationList: ChatMessageJson? = null,
|
||||
var objectType: ConversationEnums.ObjectType? = null,
|
||||
var notificationLevel: ConversationEnums.NotificationLevel? = null,
|
||||
var conversationReadOnlyState: ConversationEnums.ConversationReadOnlyState? = null,
|
||||
var lobbyState: ConversationEnums.LobbyState? = null,
|
||||
var lobbyTimer: Long? = null,
|
||||
var lastReadMessage: Int = 0,
|
||||
var hasCall: Boolean = false,
|
||||
|
@ -53,20 +60,23 @@ class ConversationModel(
|
|||
var callStartTime: Long? = null,
|
||||
var recordingConsentRequired: Int = 0,
|
||||
var remoteServer: String? = null,
|
||||
var remoteToken: String? = null
|
||||
) {
|
||||
var remoteToken: String? = null,
|
||||
override var id: Long = roomId?.toLong() ?: 0,
|
||||
override var markedForDeletion: Boolean = false
|
||||
) : SyncableModel {
|
||||
|
||||
companion object {
|
||||
fun mapToConversationModel(conversation: Conversation): ConversationModel {
|
||||
fun mapToConversationModel(conversation: Conversation, user: User): ConversationModel {
|
||||
return ConversationModel(
|
||||
internalId = user.id!!.toString() + "@" + conversation.token,
|
||||
roomId = conversation.roomId,
|
||||
token = conversation.token,
|
||||
name = conversation.name,
|
||||
displayName = conversation.displayName,
|
||||
description = conversation.description,
|
||||
type = conversation.type?.let { ConversationType.valueOf(it.name) },
|
||||
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,
|
||||
|
@ -77,18 +87,18 @@ class ConversationModel(
|
|||
unreadMessages = conversation.unreadMessages,
|
||||
unreadMention = conversation.unreadMention,
|
||||
// lastMessage = conversation.lastMessage, to do...
|
||||
objectType = conversation.objectType?.let { ObjectType.valueOf(it.name) },
|
||||
objectType = conversation.objectType?.let { ConversationEnums.ObjectType.valueOf(it.name) },
|
||||
notificationLevel = conversation.notificationLevel?.let {
|
||||
NotificationLevel.valueOf(
|
||||
ConversationEnums.NotificationLevel.valueOf(
|
||||
it.name
|
||||
)
|
||||
},
|
||||
conversationReadOnlyState = conversation.conversationReadOnlyState?.let {
|
||||
ConversationReadOnlyState.valueOf(
|
||||
ConversationEnums.ConversationReadOnlyState.valueOf(
|
||||
it.name
|
||||
)
|
||||
},
|
||||
lobbyState = conversation.lobbyState?.let { LobbyState.valueOf(it.name) },
|
||||
lobbyState = conversation.lobbyState?.let { ConversationEnums.LobbyState.valueOf(it.name) },
|
||||
lobbyTimer = conversation.lobbyTimer,
|
||||
lastReadMessage = conversation.lastReadMessage,
|
||||
hasCall = conversation.hasCall,
|
||||
|
@ -116,46 +126,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
|
||||
// }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<NotificationLevel>() {
|
||||
override fun getFromInt(i: Int): NotificationLevel {
|
||||
class DomainEnumNotificationLevelConverter : IntBasedTypeConverter<ConversationEnums.NotificationLevel>() {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||
* 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.data.changeListVersion.SyncableModel
|
||||
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"]) override 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<String?, HashMap<String?, String?>>? = 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<String, Int>? = null,
|
||||
@JsonField(name = ["reactionsSelf"]) var reactionsSelf: ArrayList<String>? = 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,
|
||||
|
||||
// override var markedForDeletion: Boolean = "comment_deleted" == messageType
|
||||
override var markedForDeletion: Boolean = false
|
||||
) : Parcelable, SyncableModel
|
|
@ -19,7 +19,7 @@ data class ChatOCS(
|
|||
@JsonField(name = ["meta"])
|
||||
var meta: GenericMeta?,
|
||||
@JsonField(name = ["data"])
|
||||
var data: List<ChatMessage>? = null
|
||||
var data: List<ChatMessageJson>? = null
|
||||
) : Parcelable {
|
||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||
constructor() : this(null, null)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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<String, ChatMessage>? = null
|
||||
var data: HashMap<String, ChatMessageJson>? = null
|
||||
) : Parcelable {
|
||||
// This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
|
||||
constructor() : this(null)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -12,9 +12,10 @@ package com.nextcloud.talk.models.json.conversations
|
|||
import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessageJson
|
||||
import com.nextcloud.talk.data.changeListVersion.SyncableModel
|
||||
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.converters.ConversationObjectTypeConverter
|
||||
import com.nextcloud.talk.models.json.converters.EnumLobbyStateConverter
|
||||
import com.nextcloud.talk.models.json.converters.EnumNotificationLevelConverter
|
||||
|
@ -39,7 +40,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)
|
||||
|
@ -67,20 +68,21 @@ data class Conversation(
|
|||
@JsonField(name = ["unreadMention"])
|
||||
var unreadMention: Boolean = false,
|
||||
|
||||
// TODO get this from Json -> map to ChatMessage and fix error
|
||||
@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,
|
||||
|
@ -149,15 +151,15 @@ 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)
|
||||
override var id: Long = 0,
|
||||
override var markedForDeletion: Boolean = false
|
||||
|
||||
) : Parcelable, SyncableModel {
|
||||
@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 +177,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 +223,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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
|
||||
* 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
|
||||
}
|
||||
}
|
|
@ -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<Conversation.ObjectType>() {
|
||||
override fun getFromString(string: String?): Conversation.ObjectType {
|
||||
class ConversationObjectTypeConverter : StringBasedTypeConverter<ConversationEnums.ObjectType>() {
|
||||
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 -> ""
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,22 +8,23 @@ 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 EnumLobbyStateConverter extends IntBasedTypeConverter<Conversation.LobbyState> {
|
||||
public class EnumLobbyStateConverter extends IntBasedTypeConverter<ConversationEnums.LobbyState> {
|
||||
@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;
|
||||
|
|
|
@ -8,26 +8,27 @@ 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 EnumNotificationLevelConverter extends IntBasedTypeConverter<Conversation.NotificationLevel> {
|
||||
public class EnumNotificationLevelConverter extends IntBasedTypeConverter<ConversationEnums.NotificationLevel> {
|
||||
@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;
|
||||
|
|
|
@ -8,22 +8,23 @@ 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 EnumReadOnlyConversationConverter extends IntBasedTypeConverter<Conversation.ConversationReadOnlyState> {
|
||||
public class EnumReadOnlyConversationConverter extends IntBasedTypeConverter<ConversationEnums.ConversationReadOnlyState> {
|
||||
@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;
|
||||
|
|
|
@ -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<Conversation.ConversationType> {
|
||||
public class EnumRoomTypeConverter extends IntBasedTypeConverter<ConversationEnums.ConversationType> {
|
||||
@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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче